chore: settings page refactoring (#2477)

* chore: implemented project layout and integrated sidebar component

* chore: implemented workspace layout and integrated sidebar component
This commit is contained in:
Anmol Singh Bhatia 2023-10-18 19:17:02 +05:30 committed by GitHub
parent 0ec0ca133a
commit c270c8689f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 1708 additions and 1747 deletions

View File

@ -15,7 +15,6 @@ type Props = {
onClick: () => void;
};
secondaryButton?: React.ReactNode;
isFullScreen?: boolean;
disabled?: boolean;
};
@ -25,10 +24,9 @@ export const EmptyState: React.FC<Props> = ({
image,
primaryButton,
secondaryButton,
isFullScreen = true,
disabled = false,
}) => (
<div className={`h-full w-full mx-auto grid place-items-center p-8 ${isFullScreen ? "md:w-4/5 lg:w-3/5" : ""}`}>
<div className={`flex items-center justify-center h-full w-full`}>
<div className="text-center flex flex-col items-center w-full">
<Image src={image} className="w-52 sm:w-60" alt={primaryButton?.text} />
<h6 className="text-xl font-semibold mt-6 sm:mt-8 mb-3">{title}</h6>

View File

@ -11,3 +11,5 @@ export * from "./projects";
export * from "./profile-preferences";
export * from "./cycles";
export * from "./modules";
export * from "./project-settings";
export * from "./workspace-settings";

View File

@ -0,0 +1,57 @@
import { FC } from "react";
import { useRouter } from "next/router";
import Link from "next/link";
import { ArrowLeft } from "lucide-react";
// ui
import { BreadcrumbItem, Breadcrumbs } from "@plane/ui";
// helper
import { truncateText } from "helpers/string.helper";
// hooks
import { useMobxStore } from "lib/mobx/store-provider";
import { observer } from "mobx-react-lite";
export interface IProjectSettingHeader {
title: string;
}
export const ProjectSettingHeader: FC<IProjectSettingHeader> = observer((props) => {
const { title } = props;
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// store
const { project: projectStore } = useMobxStore();
const projectDetails = projectId ? projectStore.project_details[projectId.toString()] : null;
return (
<div
className={`relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4`}
>
<div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis">
<div className="block md:hidden">
<button
type="button"
className="grid h-8 w-8 place-items-center rounded border border-custom-border-200"
onClick={() => router.back()}
>
<ArrowLeft fontSize={14} strokeWidth={2} />
</button>
</div>
<div>
<Breadcrumbs onBack={() => router.back()}>
<BreadcrumbItem
link={
<Link href={`/${workspaceSlug}/projects/${projectDetails?.id}/issues`}>
<a className={`border-r-2 border-custom-sidebar-border-200 px-3 text-sm `}>
<p className="truncate">{`${truncateText(projectDetails?.name ?? "Project", 32)}`}</p>
</a>
</Link>
}
/>
<BreadcrumbItem title={title} unshrinkTitle />
</Breadcrumbs>
</div>
</div>
</div>
);
});

View File

@ -0,0 +1,66 @@
import { FC } from "react";
import useSWR from "swr";
import { useRouter } from "next/router";
import Link from "next/link";
import { ArrowLeft } from "lucide-react";
// ui
import { BreadcrumbItem, Breadcrumbs } from "@plane/ui";
// hooks
import { observer } from "mobx-react-lite";
// services
import { WorkspaceService } from "services/workspace.service";
// helpers
import { truncateText } from "helpers/string.helper";
// constant
import { WORKSPACE_DETAILS } from "constants/fetch-keys";
const workspaceService = new WorkspaceService();
export interface IWorkspaceSettingHeader {
title: string;
}
export const WorkspaceSettingHeader: FC<IWorkspaceSettingHeader> = observer((props) => {
const { title } = props;
const router = useRouter();
const { workspaceSlug } = router.query;
const { data: activeWorkspace } = useSWR(workspaceSlug ? WORKSPACE_DETAILS(workspaceSlug as string) : null, () =>
workspaceSlug ? workspaceService.getWorkspace(workspaceSlug as string) : null
);
return (
<div
className={`relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4`}
>
<div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis">
<div className="block md:hidden">
<button
type="button"
className="grid h-8 w-8 place-items-center rounded border border-custom-border-200"
onClick={() => router.back()}
>
<ArrowLeft fontSize={14} strokeWidth={2} />
</button>
</div>
<div>
<Breadcrumbs onBack={() => router.back()}>
<BreadcrumbItem
link={
<Link href={`/${workspaceSlug}`}>
<a className={`border-r-2 border-custom-sidebar-border-200 px-3 text-sm `}>
<p className="truncate">{`${truncateText(activeWorkspace?.name ?? "Workspace", 32)}`}</p>
</a>
</Link>
}
/>
<BreadcrumbItem title={title} unshrinkTitle />
</Breadcrumbs>
</div>
</div>
</div>
);
});

View File

@ -173,7 +173,6 @@ export const NotificationPopover = () => {
title="You're updated with all the notifications"
description="You have read all the notifications."
image={emptyNotification}
isFullScreen={false}
/>
</div>
)

View File

@ -0,0 +1,63 @@
import React from "react";
// ui
import { Disclosure, Transition } from "@headlessui/react";
import { Button, Loader } from "@plane/ui";
// icons
import { ChevronDown, ChevronUp } from "lucide-react";
// types
import { IProject } from "types";
export interface IDeleteProjectSection {
projectDetails: IProject;
handleDelete: () => void;
}
export const DeleteProjectSection: React.FC<IDeleteProjectSection> = (props) => {
const { projectDetails, handleDelete } = props;
return (
<Disclosure as="div" className="border-t border-custom-border-400">
{({ open }) => (
<div className="w-full">
<Disclosure.Button as="button" type="button" className="flex items-center justify-between w-full py-4">
<span className="text-xl tracking-tight">Delete Project</span>
{open ? <ChevronUp className="h-5 w-5" /> : <ChevronDown className="h-5 w-5" />}
</Disclosure.Button>
<Transition
show={open}
enter="transition duration-100 ease-out"
enterFrom="transform opacity-0"
enterTo="transform opacity-100"
leave="transition duration-75 ease-out"
leaveFrom="transform opacity-100"
leaveTo="transform opacity-0"
>
<Disclosure.Panel>
<div className="flex flex-col gap-8">
<span className="text-sm tracking-tight">
The danger zone of the project delete page is a critical area that requires careful consideration and
attention. When deleting a project, all of the data and resources within that project will be
permanently removed and cannot be recovered.
</span>
<div>
{projectDetails ? (
<div>
<Button variant="danger" onClick={handleDelete}>
Delete my project
</Button>
</div>
) : (
<Loader className="mt-2 w-full">
<Loader.Item height="38px" width="144px" />
</Loader>
)}
</div>
</div>
</Disclosure.Panel>
</Transition>
</div>
)}
</Disclosure>
);
};

View File

@ -14,3 +14,4 @@ export * from "./card";
export * from "./join-project-modal";
export * from "./form";
export * from "./form-loader";
export * from "./delete-project-section";

View File

@ -0,0 +1,2 @@
export * from "./layout";
export * from "./sidebar";

View File

@ -0,0 +1,38 @@
import { FC, ReactNode } from "react";
// layouts
import { UserAuthWrapper, ProjectAuthWrapper } from "layouts/auth-layout";
// components
import { AppSidebar } from "layouts/app-layout";
import { ProjectSettingsSidebar } from "./sidebar";
export interface IProjectSettingLayout {
children: ReactNode;
header: ReactNode;
}
export const ProjectSettingLayout: FC<IProjectSettingLayout> = (props) => {
const { children, header } = props;
return (
<>
<UserAuthWrapper>
<ProjectAuthWrapper>
<div className="relative flex h-screen w-full overflow-hidden">
<AppSidebar />
<main className="relative flex flex-col h-full w-full overflow-hidden bg-custom-background-100">
{header}
<div className="h-full w-full overflow-hidden">
<div className="flex gap-2 relative h-full w-full overflow-x-hidden overflow-y-scroll">
<div className="w-80 pt-8 overflow-y-hidden flex-shrink-0">
<ProjectSettingsSidebar />
</div>
{children}
</div>
</div>
</main>
</div>
</ProjectAuthWrapper>
</UserAuthWrapper>
</>
);
};

View File

@ -0,0 +1,70 @@
import React from "react";
import { useRouter } from "next/router";
import Link from "next/link";
export const ProjectSettingsSidebar = () => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const projectLinks: Array<{
label: string;
href: string;
}> = [
{
label: "General",
href: `/${workspaceSlug}/projects/${projectId}/settings`,
},
{
label: "Members",
href: `/${workspaceSlug}/projects/${projectId}/settings/members`,
},
{
label: "Features",
href: `/${workspaceSlug}/projects/${projectId}/settings/features`,
},
{
label: "States",
href: `/${workspaceSlug}/projects/${projectId}/settings/states`,
},
{
label: "Labels",
href: `/${workspaceSlug}/projects/${projectId}/settings/labels`,
},
{
label: "Integrations",
href: `/${workspaceSlug}/projects/${projectId}/settings/integrations`,
},
{
label: "Estimates",
href: `/${workspaceSlug}/projects/${projectId}/settings/estimates`,
},
{
label: "Automations",
href: `/${workspaceSlug}/projects/${projectId}/settings/automations`,
},
];
return (
<div className="flex flex-col gap-6 w-80 px-5">
<div className="flex flex-col gap-2">
<span className="text-xs text-custom-sidebar-text-400 font-semibold">SETTINGS</span>
<div className="flex flex-col gap-1 w-full">
{projectLinks.map((link) => (
<Link key={link.href} href={link.href}>
<a>
<div
className={`px-4 py-2 text-sm font-medium rounded-md ${
(link.label === "Import" ? router.asPath.includes(link.href) : router.asPath === link.href)
? "bg-custom-primary-100/10 text-custom-primary-100"
: "text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80 focus:bg-custom-sidebar-background-80"
}`}
>
{link.label}
</div>
</a>
</Link>
))}
</div>
</div>
</div>
);
};

View File

@ -0,0 +1,2 @@
export * from "./layout";
export * from "./sidebar";

View File

@ -0,0 +1,38 @@
import { FC, ReactNode } from "react";
// layouts
import { UserAuthWrapper, WorkspaceAuthWrapper } from "layouts/auth-layout";
// components
import { AppSidebar } from "layouts/app-layout";
import { WorkspaceSettingsSidebar } from "./sidebar";
export interface IWorkspaceSettingLayout {
children: ReactNode;
header: ReactNode;
}
export const WorkspaceSettingLayout: FC<IWorkspaceSettingLayout> = (props) => {
const { children, header } = props;
return (
<>
<UserAuthWrapper>
<WorkspaceAuthWrapper>
<div className="relative flex h-screen w-full overflow-hidden">
<AppSidebar />
<main className="relative flex flex-col h-full w-full overflow-hidden bg-custom-background-100">
{header}
<div className="h-full w-full overflow-hidden">
<div className="flex gap-2 relative h-full w-full overflow-x-hidden overflow-y-scroll">
<div className="w-80 pt-8 overflow-y-hidden flex-shrink-0">
<WorkspaceSettingsSidebar />
</div>
{children}
</div>
</div>
</main>
</div>
</WorkspaceAuthWrapper>
</UserAuthWrapper>
</>
);
};

View File

@ -0,0 +1,101 @@
import React from "react";
import { useRouter } from "next/router";
import Link from "next/link";
export const WorkspaceSettingsSidebar = () => {
const router = useRouter();
const { workspaceSlug } = router.query;
const workspaceLinks: Array<{
label: string;
href: string;
}> = [
{
label: "General",
href: `/${workspaceSlug}/settings`,
},
{
label: "Members",
href: `/${workspaceSlug}/settings/members`,
},
{
label: "Billing & Plans",
href: `/${workspaceSlug}/settings/billing`,
},
{
label: "Integrations",
href: `/${workspaceSlug}/settings/integrations`,
},
{
label: "Imports",
href: `/${workspaceSlug}/settings/imports`,
},
{
label: "Exports",
href: `/${workspaceSlug}/settings/exports`,
},
];
const profileLinks: Array<{
label: string;
href: string;
}> = [
{
label: "Profile",
href: `/${workspaceSlug}/me/profile`,
},
{
label: "Activity",
href: `/${workspaceSlug}/me/profile/activity`,
},
{
label: "Preferences",
href: `/${workspaceSlug}/me/profile/preferences`,
},
];
return (
<div className="flex flex-col gap-6 w-80 px-5">
<div className="flex flex-col gap-2">
<span className="text-xs text-custom-sidebar-text-400 font-semibold">SETTINGS</span>
<div className="flex flex-col gap-1 w-full">
{workspaceLinks.map((link) => (
<Link key={link.href} href={link.href}>
<a>
<div
className={`px-4 py-2 text-sm font-medium rounded-md ${
(link.label === "Import" ? router.asPath.includes(link.href) : router.asPath === link.href)
? "bg-custom-primary-100/10 text-custom-primary-100"
: "text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80 focus:bg-custom-sidebar-background-80"
}`}
>
{link.label}
</div>
</a>
</Link>
))}
</div>
</div>
<div className="flex flex-col gap-2">
<span className="text-xs text-custom-sidebar-text-400 font-semibold">My Account</span>
<div className="flex flex-col gap-1 w-full">
{profileLinks.map((link) => (
<Link key={link.href} href={link.href}>
<a>
<div
className={`px-4 py-2 text-sm font-medium rounded-md ${
(link.label === "Import" ? router.asPath.includes(link.href) : router.asPath === link.href)
? "bg-custom-primary-100/10 text-custom-primary-100"
: "text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80 focus:bg-custom-sidebar-background-80"
}`}
>
{link.label}
</div>
</a>
</Link>
))}
</div>
</div>
</div>
);
};

View File

@ -4,19 +4,19 @@ import Link from "next/link";
// services
import { UserService } from "services/user.service";
// layouts
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout-legacy";
import { WorkspaceSettingLayout } from "layouts/setting-layout/workspace-setting-layout";
// components
import { ActivityIcon, ActivityMessage } from "components/core";
import { RichReadOnlyEditor } from "@plane/rich-text-editor";
import { WorkspaceSettingHeader } from "components/headers";
// icons
import { History, MessageSquare } from "lucide-react";
// ui
import { BreadcrumbItem, Breadcrumbs, ExternalLinkIcon, Loader } from "@plane/ui";
import { ExternalLinkIcon, Loader } from "@plane/ui";
// fetch-keys
import { USER_ACTIVITY } from "constants/fetch-keys";
// helper
import { timeAgo } from "helpers/date-time.helper";
import { SettingsSidebar } from "components/project";
const userService = new UserService();
@ -30,18 +30,7 @@ const ProfileActivity = () => {
);
return (
<WorkspaceAuthorizationLayout
breadcrumbs={
<Breadcrumbs onBack={() => router.back()}>
<BreadcrumbItem title="My Profile Activity" />
</Breadcrumbs>
}
>
<div className="flex flex-row gap-2 h-full">
<div className="w-80 pt-8 overflow-y-hidden flex-shrink-0">
<SettingsSidebar />
</div>
<WorkspaceSettingLayout header={<WorkspaceSettingHeader title="My Profile Activity" />}>
{userActivity ? (
<section className="pr-9 py-8 w-full overflow-y-auto">
<div className="flex items-center py-3.5 border-b border-custom-border-200">
@ -141,8 +130,7 @@ const ProfileActivity = () => {
) : (
<ActivityIcon activity={activityItem} />
)
) : activityItem.actor_detail.avatar &&
activityItem.actor_detail.avatar !== "" ? (
) : activityItem.actor_detail.avatar && activityItem.actor_detail.avatar !== "" ? (
<img
src={activityItem.actor_detail.avatar}
alt={activityItem.actor_detail.display_name}
@ -174,8 +162,7 @@ const ProfileActivity = () => {
<a className="text-gray font-medium">{activityItem.actor_detail.display_name}</a>
</Link>
)}{" "}
{message}{" "}
<span className="whitespace-nowrap">{timeAgo(activityItem.created_at)}</span>
{message} <span className="whitespace-nowrap">{timeAgo(activityItem.created_at)}</span>
</div>
</div>
</>
@ -196,8 +183,7 @@ const ProfileActivity = () => {
<Loader.Item height="40px" />
</Loader>
)}
</div>
</WorkspaceAuthorizationLayout>
</WorkspaceSettingLayout>
);
};

View File

@ -9,12 +9,12 @@ import { UserService } from "services/user.service";
import useUserAuth from "hooks/use-user-auth";
import useToast from "hooks/use-toast";
// layouts
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout-legacy";
import { WorkspaceSettingLayout } from "layouts/setting-layout/workspace-setting-layout";
// components
import { ImagePickerPopover, ImageUploadModal } from "components/core";
import { SettingsSidebar } from "components/project";
import { WorkspaceSettingHeader } from "components/headers";
// ui
import { BreadcrumbItem, Breadcrumbs, Button, Input, Spinner } from "@plane/ui";
import { Button, Input, Spinner } from "@plane/ui";
import { CustomSearchSelect, CustomSelect } from "components/ui";
// icons
import { User2, UserCircle2 } from "lucide-react";
@ -144,13 +144,7 @@ const Profile: NextPage = () => {
}));
return (
<WorkspaceAuthorizationLayout
breadcrumbs={
<Breadcrumbs onBack={() => router.back()}>
<BreadcrumbItem title="My Profile" />
</Breadcrumbs>
}
>
<WorkspaceSettingLayout header={<WorkspaceSettingHeader title="My Profile" />}>
<ImageUploadModal
isOpen={isImageUploadModalOpen}
onClose={() => setIsImageUploadModalOpen(false)}
@ -166,10 +160,6 @@ const Profile: NextPage = () => {
/>
{myProfile ? (
<form onSubmit={handleSubmit(onSubmit)}>
<div className="flex flex-row gap-2 h-full">
<div className="w-80 pt-8 overflow-y-hidden flex-shrink-0">
<SettingsSidebar />
</div>
<div className={`flex flex-col gap-8 pr-9 py-9 w-full overflow-y-auto`}>
<div className="relative h-44 w-full mt-6">
<img
@ -393,14 +383,13 @@ const Profile: NextPage = () => {
</div>
</div>
</div>
</div>
</form>
) : (
<div className="grid h-full w-full place-items-center px-4 sm:px-0">
<Spinner />
</div>
)}
</WorkspaceAuthorizationLayout>
</WorkspaceSettingLayout>
);
};

View File

@ -5,11 +5,10 @@ import { useTheme } from "next-themes";
import { useMobxStore } from "lib/mobx/store-provider";
import useToast from "hooks/use-toast";
// layouts
import { AppLayout } from "layouts/app-layout";
import { WorkspaceSettingLayout } from "layouts/setting-layout/workspace-setting-layout";
// components
import { CustomThemeSelector, ThemeSwitch } from "components/core";
import { SettingsSidebar } from "components/project";
import { ProfilePreferencesHeader } from "components/headers";
import { WorkspaceSettingHeader } from "components/headers";
// ui
import { Spinner } from "@plane/ui";
// constants
@ -45,14 +44,8 @@ const ProfilePreferencesPage = observer(() => {
};
return (
<AppLayout header={<ProfilePreferencesHeader />}>
<>
<WorkspaceSettingLayout header={<WorkspaceSettingHeader title="My Profile Preferences" />}>
{userStore.currentUser ? (
<div className="flex flex-row gap-2 h-full">
<div className="w-80 pt-8 overflow-y-hidden flex-shrink-0">
<SettingsSidebar />
</div>
<div className="pr-9 py-8 w-full overflow-y-auto">
<div className="flex items-center py-3.5 border-b border-custom-border-200">
<h3 className="text-xl font-medium">Preferences</h3>
@ -68,14 +61,12 @@ const ProfilePreferencesPage = observer(() => {
</div>
{userTheme?.theme === "custom" && <CustomThemeSelector />}
</div>
</div>
) : (
<div className="grid h-full w-full place-items-center px-4 sm:px-0">
<Spinner />
</div>
)}
</>
</AppLayout>
</WorkspaceSettingLayout>
);
});

View File

@ -1,30 +1,25 @@
import React from "react";
import { useRouter } from "next/router";
import Link from "next/link";
import useSWR, { mutate } from "swr";
// services
import { ProjectService } from "services/project";
// layouts
import { ProjectAuthorizationWrapper } from "layouts/auth-layout-legacy";
import { ProjectSettingLayout } from "layouts/setting-layout/project-setting-layout";
// hooks
import useUserAuth from "hooks/use-user-auth";
import useProjectDetails from "hooks/use-project-details";
import useToast from "hooks/use-toast";
// components
import { AutoArchiveAutomation, AutoCloseAutomation } from "components/automation";
import { SettingsSidebar } from "components/project";
// ui
import { BreadcrumbItem, Breadcrumbs } from "@plane/ui";
import { ProjectSettingHeader } from "components/headers";
// types
import type { NextPage } from "next";
import { IProject } from "types";
// constant
import { PROJECTS_LIST, PROJECT_DETAILS, USER_PROJECT_VIEW } from "constants/fetch-keys";
// helper
import { truncateText } from "helpers/string.helper";
// services
const projectService = new ProjectService();
@ -75,26 +70,7 @@ const AutomationsSettings: NextPage = () => {
const isAdmin = memberDetails?.role === 20;
return (
<ProjectAuthorizationWrapper
breadcrumbs={
<Breadcrumbs onBack={() => router.back()}>
<BreadcrumbItem
link={
<Link href={`/${workspaceSlug}/projects/${projectDetails?.id}/issues`}>
<a className={`border-r-2 border-custom-sidebar-border-200 px-3 text-sm `}>
<p className="truncate">{`${truncateText(projectDetails?.name ?? "Project", 32)}`}</p>
</a>
</Link>
}
/>
<BreadcrumbItem title="Automations Settings" unshrinkTitle />
</Breadcrumbs>
}
>
<div className="flex flex-row gap-2 h-full">
<div className="w-80 pt-8 overflow-y-hidden flex-shrink-0">
<SettingsSidebar />
</div>
<ProjectSettingLayout header={<ProjectSettingHeader title="Automations Settings" />}>
<section className={`pr-9 py-8 w-full overflow-y-auto ${isAdmin ? "" : "opacity-60"}`}>
<div className="flex items-center py-3.5 border-b border-custom-border-200">
<h3 className="text-xl font-medium">Automations</h3>
@ -102,8 +78,7 @@ const AutomationsSettings: NextPage = () => {
<AutoArchiveAutomation projectDetails={projectDetails} handleChange={handleChange} disabled={!isAdmin} />
<AutoCloseAutomation projectDetails={projectDetails} handleChange={handleChange} disabled={!isAdmin} />
</section>
</div>
</ProjectAuthorizationWrapper>
</ProjectSettingLayout>
);
};

View File

@ -1,21 +1,21 @@
import React, { useState } from "react";
import { useRouter } from "next/router";
import Link from "next/link";
import useSWR, { mutate } from "swr";
// services
import { ProjectService, ProjectEstimateService } from "services/project";
// hooks
import useProjectDetails from "hooks/use-project-details";
// layouts
import { ProjectAuthorizationWrapper } from "layouts/auth-layout-legacy";
import { ProjectSettingLayout } from "layouts/setting-layout/project-setting-layout";
// components
import { CreateUpdateEstimateModal, SingleEstimate } from "components/estimates";
import { SettingsSidebar } from "components/project";
import { ProjectSettingHeader } from "components/headers";
//hooks
import useToast from "hooks/use-toast";
import useUserAuth from "hooks/use-user-auth";
// ui
import { BreadcrumbItem, Breadcrumbs, Button, Loader } from "@plane/ui";
import { Button, Loader } from "@plane/ui";
import { EmptyState } from "components/common";
// icons
import { Plus } from "lucide-react";
@ -26,8 +26,6 @@ import { IEstimate, IProject } from "types";
import type { NextPage } from "next";
// fetch-keys
import { ESTIMATES_LIST, PROJECT_DETAILS } from "constants/fetch-keys";
// helper
import { truncateText } from "helpers/string.helper";
// services
const projectService = new ProjectService();
@ -110,28 +108,10 @@ const EstimatesSettings: NextPage = () => {
}}
user={user}
/>
<ProjectAuthorizationWrapper
breadcrumbs={
<Breadcrumbs onBack={() => router.back()}>
<BreadcrumbItem
link={
<Link href={`/${workspaceSlug}/projects/${projectDetails?.id}/issues`}>
<a className={`border-r-2 border-custom-sidebar-border-200 px-3 text-sm `}>
<p className="truncate">{`${truncateText(projectDetails?.name ?? "Project", 32)}`}</p>
</a>
</Link>
}
/>
<BreadcrumbItem title="Estimates Settings" unshrinkTitle />
</Breadcrumbs>
}
>
<div className="flex flex-row gap-2 h-full">
<div className="w-80 pt-8 overflow-y-hidden flex-shrink-0">
<SettingsSidebar />
</div>
<div className="pr-9 py-8 flex flex-col w-full overflow-y-auto">
<section className="flex items-center justify-between pt-2 pb-3.5 border-b border-custom-border-200">
<ProjectSettingLayout header={<ProjectSettingHeader title="Estimates Settings" />}>
<div className="pr-9 py-8 w-full overflow-y-auto">
<section className="flex items-center justify-between py-3.5 border-b border-custom-border-200">
<h3 className="text-xl font-medium">Estimates</h3>
<div className="col-span-12 space-y-5 sm:col-span-7">
<div className="flex items-center gap-2">
@ -191,8 +171,7 @@ const EstimatesSettings: NextPage = () => {
</Loader>
)}
</div>
</div>
</ProjectAuthorizationWrapper>
</ProjectSettingLayout>
</>
);
};

View File

@ -1,20 +1,19 @@
import React from "react";
import { useRouter } from "next/router";
import Link from "next/link";
import useSWR, { mutate } from "swr";
// services
import { ProjectService } from "services/project";
import { TrackEventService, MiscellaneousEventType } from "services/track_event.service";
// layouts
import { ProjectAuthorizationWrapper } from "layouts/auth-layout-legacy";
import { ProjectSettingLayout } from "layouts/setting-layout/project-setting-layout";
// hooks
import useToast from "hooks/use-toast";
import useUserAuth from "hooks/use-user-auth";
// components
import { SettingsSidebar } from "components/project";
import { ProjectSettingHeader } from "components/headers";
// ui
import { BreadcrumbItem, Breadcrumbs, ContrastIcon, DiceIcon, ToggleSwitch } from "@plane/ui";
import { ContrastIcon, DiceIcon, ToggleSwitch } from "@plane/ui";
// icons
import { FileText, Inbox, Layers } from "lucide-react";
// types
@ -22,8 +21,6 @@ import { IProject, IUser } from "types";
import type { NextPage } from "next";
// fetch-keys
import { PROJECTS_LIST, PROJECT_DETAILS, USER_PROJECT_VIEW } from "constants/fetch-keys";
// helper
import { truncateText } from "helpers/string.helper";
const featuresList = [
{
@ -139,26 +136,7 @@ const FeaturesSettings: NextPage = () => {
const isAdmin = memberDetails?.role === 20;
return (
<ProjectAuthorizationWrapper
breadcrumbs={
<Breadcrumbs onBack={() => router.back()}>
<BreadcrumbItem
link={
<Link href={`/${workspaceSlug}/projects/${projectDetails?.id}/issues`}>
<a className={`border-r-2 border-custom-sidebar-border-200 px-3 text-sm `}>
<p className="truncate">{`${truncateText(projectDetails?.name ?? "Project", 32)}`}</p>
</a>
</Link>
}
/>
<BreadcrumbItem title="Features Settings" unshrinkTitle />
</Breadcrumbs>
}
>
<div className="flex flex-row gap-2 h-full">
<div className="w-80 pt-8 overflow-y-hidden flex-shrink-0">
<SettingsSidebar />
</div>
<ProjectSettingLayout header={<ProjectSettingHeader title="Features Settings" />}>
<section className={`pr-9 py-8 w-full overflow-y-auto ${isAdmin ? "" : "opacity-60"}`}>
<div className="flex items-center py-3.5 border-b border-custom-border-200">
<h3 className="text-xl font-medium">Features</h3>
@ -203,8 +181,7 @@ const FeaturesSettings: NextPage = () => {
))}
</div>
</section>
</div>
</ProjectAuthorizationWrapper>
</ProjectSettingLayout>
);
};

View File

@ -1,28 +1,25 @@
import { useState } from "react";
import { useRouter } from "next/router";
import Link from "next/link";
import useSWR from "swr";
import { Disclosure, Transition } from "@headlessui/react";
// layouts
import { ProjectAuthorizationWrapper } from "layouts/auth-layout-legacy";
// services
import { ProjectService } from "services/project";
import { ProjectSettingLayout } from "layouts/setting-layout/project-setting-layout";
// components
import { DeleteProjectModal, ProjectDetailsForm, ProjectDetailsFormLoader, SettingsSidebar } from "components/project";
import { BreadcrumbItem, Breadcrumbs, Button, Loader } from "@plane/ui";
import { ChevronDown, ChevronUp } from "lucide-react";
// helpers
import { truncateText } from "helpers/string.helper";
import { ProjectSettingHeader } from "components/headers";
import {
DeleteProjectModal,
DeleteProjectSection,
ProjectDetailsForm,
ProjectDetailsFormLoader,
} from "components/project";
// types
import type { NextPage } from "next";
// fetch-keys
import { USER_PROJECT_VIEW } from "constants/fetch-keys";
import { useMobxStore } from "lib/mobx/store-provider";
import { observer } from "mobx-react-lite";
// services
const projectService = new ProjectService();
// const projectService = new ProjectService();
const GeneralSettings: NextPage = observer(() => {
const { project: projectStore } = useMobxStore();
@ -33,9 +30,6 @@ const GeneralSettings: NextPage = observer(() => {
const { workspaceSlug, projectId } = router.query;
// derived values
const projectDetails = projectId ? projectStore.project_details[projectId.toString()] : null;
console.log("projectDetails", projectDetails);
console.log("condition", workspaceSlug && projectId && !projectDetails);
console.log("wow", projectId);
// api call to fetch project details
useSWR(
workspaceSlug && projectId ? "PROJECT_DETAILS" : null,
@ -43,36 +37,15 @@ const GeneralSettings: NextPage = observer(() => {
? () => projectStore.fetchProjectDetails(workspaceSlug.toString(), projectId.toString())
: null
);
// API call to fetch user privileges
const { data: memberDetails } = useSWR(
workspaceSlug && projectId ? USER_PROJECT_VIEW(projectId.toString()) : null,
workspaceSlug && projectId
? () => projectService.projectMemberMe(workspaceSlug.toString(), projectId.toString())
: null
);
// const currentNetwork = NETWORK_CHOICES.find((n) => n.key === projectDetails?.network);
// const selectedNetwork = NETWORK_CHOICES.find((n) => n.key === watch("network"));
const isAdmin = memberDetails?.role === 20;
const isAdmin = projectDetails?.member_role === 20;
console.log("isAdmin", isAdmin);
return (
<ProjectAuthorizationWrapper
breadcrumbs={
<Breadcrumbs onBack={() => router.back()}>
<BreadcrumbItem
link={
<Link href={`/${workspaceSlug}/projects/${projectDetails?.id}/issues`}>
<a className={`border-r-2 border-custom-sidebar-border-200 px-3 text-sm `}>
<p className="truncate">{`${truncateText(projectDetails?.name ?? "Project", 32)}`}</p>
</a>
</Link>
}
/>
<BreadcrumbItem title="General Settings" unshrinkTitle />
</Breadcrumbs>
}
>
<ProjectSettingLayout header={<ProjectSettingHeader title="General Settings" />}>
{projectDetails && (
<DeleteProjectModal
project={projectDetails}
@ -81,10 +54,6 @@ const GeneralSettings: NextPage = observer(() => {
/>
)}
<div className="flex flex-row gap-2 h-full">
<div className="w-80 pt-8 overflow-y-hidden flex-shrink-0">
<SettingsSidebar />
</div>
<div className={`pr-9 py-8 w-full overflow-y-auto ${isAdmin ? "" : "opacity-60"}`}>
{projectDetails && workspaceSlug ? (
<ProjectDetailsForm project={projectDetails} workspaceSlug={workspaceSlug.toString()} isAdmin={isAdmin} />
@ -93,57 +62,13 @@ const GeneralSettings: NextPage = observer(() => {
)}
{isAdmin && (
<Disclosure as="div" className="border-t border-custom-border-400">
{({ open }) => (
<div className="w-full">
<Disclosure.Button
as="button"
type="button"
className="flex items-center justify-between w-full py-4"
>
<span className="text-xl tracking-tight">Delete Project</span>
{open ? <ChevronUp className="h-5 w-5" /> : <ChevronDown className="h-5 w-5" />}
</Disclosure.Button>
<Transition
show={open}
enter="transition duration-100 ease-out"
enterFrom="transform opacity-0"
enterTo="transform opacity-100"
leave="transition duration-75 ease-out"
leaveFrom="transform opacity-100"
leaveTo="transform opacity-0"
>
<Disclosure.Panel>
<div className="flex flex-col gap-8">
<span className="text-sm tracking-tight">
The danger zone of the project delete page is a critical area that requires careful
consideration and attention. When deleting a project, all of the data and resources within
that project will be permanently removed and cannot be recovered.
</span>
<div>
{projectDetails ? (
<div>
<Button variant="danger" onClick={() => setSelectedProject(projectDetails.id ?? null)}>
Delete my project
</Button>
</div>
) : (
<Loader className="mt-2 w-full">
<Loader.Item height="38px" width="144px" />
</Loader>
<DeleteProjectSection
projectDetails={projectDetails}
handleDelete={() => setSelectedProject(projectDetails.id ?? null)}
/>
)}
</div>
</div>
</Disclosure.Panel>
</Transition>
</div>
)}
</Disclosure>
)}
</div>
</div>
</ProjectAuthorizationWrapper>
</ProjectSettingLayout>
);
});

View File

@ -1,29 +1,29 @@
import React from "react";
import { useRouter } from "next/router";
import Link from "next/link";
import useSWR from "swr";
// layouts
import { ProjectAuthorizationWrapper } from "layouts/auth-layout-legacy";
import { ProjectSettingLayout } from "layouts/setting-layout/project-setting-layout";
// services
import { IntegrationService } from "services/integrations";
import { ProjectService } from "services/project";
// components
import { SettingsSidebar, SingleIntegration } from "components/project";
import { SingleIntegration } from "components/project";
import { ProjectSettingHeader } from "components/headers";
// ui
import { EmptyState } from "components/common";
import { BreadcrumbItem, Breadcrumbs, Loader } from "@plane/ui";
import { Loader } from "@plane/ui";
// images
import emptyIntegration from "public/empty-state/integration.svg";
// types
import { IProject } from "types";
import type { NextPage } from "next";
// fetch-keys
import { PROJECT_DETAILS, USER_PROJECT_VIEW, WORKSPACE_INTEGRATIONS } from "constants/fetch-keys";
import { PROJECT_DETAILS, WORKSPACE_INTEGRATIONS } from "constants/fetch-keys";
// helper
import { truncateText } from "helpers/string.helper";
// import { useMobxStore } from "lib/mobx/store-provider";
// services
const integrationService = new IntegrationService();
@ -33,6 +33,10 @@ const ProjectIntegrations: NextPage = () => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
// const { project: projectStore } = useMobxStore();
// const projectDetails = projectId ? projectStore.project_details[projectId.toString()] : null;
const { data: projectDetails } = useSWR<IProject>(
workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
workspaceSlug && projectId ? () => projectService.getProject(workspaceSlug as string, projectId as string) : null
@ -43,36 +47,10 @@ const ProjectIntegrations: NextPage = () => {
() => (workspaceSlug ? integrationService.getWorkspaceIntegrationsList(workspaceSlug as string) : null)
);
const { data: memberDetails } = useSWR(
workspaceSlug && projectId ? USER_PROJECT_VIEW(projectId.toString()) : null,
workspaceSlug && projectId
? () => projectService.projectMemberMe(workspaceSlug.toString(), projectId.toString())
: null
);
const isAdmin = memberDetails?.role === 20;
const isAdmin = projectDetails?.member_role === 20;
return (
<ProjectAuthorizationWrapper
breadcrumbs={
<Breadcrumbs onBack={() => router.back()}>
<BreadcrumbItem
link={
<Link href={`/${workspaceSlug}/projects/${projectId}/issues`}>
<a className={`border-r-2 border-custom-sidebar-border-200 px-3 text-sm `}>
<p className="truncate">{`${truncateText(projectDetails?.name ?? "Project", 32)}`}</p>
</a>
</Link>
}
/>
<BreadcrumbItem title="Integrations Settings" unshrinkTitle />
</Breadcrumbs>
}
>
<div className="flex flex-row gap-2 h-full">
<div className="w-80 pt-8 overflow-y-hidden flex-shrink-0">
<SettingsSidebar />
</div>
<ProjectSettingLayout header={<ProjectSettingHeader title="Integrations Settings" />}>
<div className={`pr-9 py-8 gap-10 w-full overflow-y-auto ${isAdmin ? "" : "opacity-60"}`}>
<div className="flex items-center py-3.5 border-b border-custom-border-200">
<h3 className="text-xl font-medium">Integrations</h3>
@ -105,8 +83,7 @@ const ProjectIntegrations: NextPage = () => {
</Loader>
)}
</div>
</div>
</ProjectAuthorizationWrapper>
</ProjectSettingLayout>
);
};

View File

@ -1,17 +1,15 @@
import React, { useState, useRef } from "react";
import { useRouter } from "next/router";
import Link from "next/link";
import useSWR from "swr";
// hooks
import useUserAuth from "hooks/use-user-auth";
// services
import { ProjectService } from "services/project";
import { IssueLabelService } from "services/issue";
// layouts
import { ProjectAuthorizationWrapper } from "layouts/auth-layout-legacy";
import { ProjectSettingLayout } from "layouts/setting-layout/project-setting-layout";
// components
import {
CreateUpdateLabelInline,
@ -20,9 +18,9 @@ import {
SingleLabel,
SingleLabelGroup,
} from "components/labels";
import { SettingsSidebar } from "components/project";
import { ProjectSettingHeader } from "components/headers";
// ui
import { BreadcrumbItem, Breadcrumbs, Button, Loader } from "@plane/ui";
import { Button, Loader } from "@plane/ui";
import { EmptyState } from "components/common";
// images
import emptyLabel from "public/empty-state/label.svg";
@ -30,12 +28,9 @@ import emptyLabel from "public/empty-state/label.svg";
import { IIssueLabels } from "types";
import type { NextPage } from "next";
// fetch-keys
import { PROJECT_DETAILS, PROJECT_ISSUE_LABELS } from "constants/fetch-keys";
// helper
import { truncateText } from "helpers/string.helper";
import { PROJECT_ISSUE_LABELS } from "constants/fetch-keys";
// services
const projectService = new ProjectService();
const issueLabelService = new IssueLabelService();
const LabelsSettings: NextPage = () => {
@ -60,11 +55,6 @@ const LabelsSettings: NextPage = () => {
const scrollToRef = useRef<HTMLDivElement>(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 { data: issueLabels } = useSWR(
workspaceSlug && projectId ? PROJECT_ISSUE_LABELS(projectId as string) : null,
workspaceSlug && projectId
@ -102,28 +92,9 @@ const LabelsSettings: NextPage = () => {
onClose={() => setSelectDeleteLabel(null)}
user={user}
/>
<ProjectAuthorizationWrapper
breadcrumbs={
<Breadcrumbs onBack={() => router.back()}>
<BreadcrumbItem
link={
<Link href={`/${workspaceSlug}/projects/${projectDetails?.id}/issues`}>
<a className={`border-r-2 border-custom-sidebar-border-200 px-3 text-sm `}>
<p className="truncate">{`${truncateText(projectDetails?.name ?? "Project", 32)}`}</p>
</a>
</Link>
}
/>
<BreadcrumbItem title="Labels Settings" unshrinkTitle />
</Breadcrumbs>
}
>
<div className="flex flex-row gap-2 h-full">
<div className="w-80 pt-8 overflow-y-hidden flex-shrink-0">
<SettingsSidebar />
</div>
<ProjectSettingLayout header={<ProjectSettingHeader title="Labels Settings" />}>
<section className="pr-9 py-8 gap-10 w-full overflow-y-auto">
<div className="flex items-center justify-between pt-2 pb-3.5 border-b border-custom-border-200">
<div className="flex items-center justify-between py-3.5 border-b border-custom-border-200">
<h3 className="text-xl font-medium">Labels</h3>
<Button variant="primary" onClick={newLabel} size="sm">
@ -194,7 +165,6 @@ const LabelsSettings: NextPage = () => {
text: "Add label",
onClick: () => newLabel(),
}}
isFullScreen={false}
/>
)
) : (
@ -208,8 +178,7 @@ const LabelsSettings: NextPage = () => {
</>
</div>
</section>
</div>
</ProjectAuthorizationWrapper>
</ProjectSettingLayout>
</>
);
};

View File

@ -12,13 +12,14 @@ 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-legacy";
import { ProjectSettingLayout } from "layouts/setting-layout/project-setting-layout";
// components
import ConfirmProjectMemberRemove from "components/project/confirm-project-member-remove";
import SendProjectInvitationModal from "components/project/send-project-invitation-modal";
import { MemberSelect, SettingsSidebar } from "components/project";
import { MemberSelect } from "components/project";
import { ProjectSettingHeader } from "components/headers";
// ui
import { BreadcrumbItem, Breadcrumbs, Button, Loader } from "@plane/ui";
import { Button, Loader } from "@plane/ui";
import { CustomMenu, CustomSelect } from "components/ui";
// icons
import { ChevronDown, X } from "lucide-react";
@ -36,8 +37,6 @@ import {
} from "constants/fetch-keys";
// constants
import { ROLE } from "constants/workspace";
// helper
import { truncateText } from "helpers/string.helper";
const defaultValues: Partial<IProject> = {
project_lead: null,
@ -198,22 +197,7 @@ const MembersSettings: NextPage = () => {
const isAdmin = memberDetails?.role === 20;
return (
<ProjectAuthorizationWrapper
breadcrumbs={
<Breadcrumbs onBack={() => router.back()}>
<BreadcrumbItem
link={
<Link href={`/${workspaceSlug}/projects/${projectDetails?.id}/issues`}>
<a className={`border-r-2 border-custom-sidebar-border-200 px-3 text-sm `}>
<p className="truncate">{`${truncateText(projectDetails?.name ?? "Project", 32)}`}</p>
</a>
</Link>
}
/>
<BreadcrumbItem title="Members Settings" unshrinkTitle />
</Breadcrumbs>
}
>
<ProjectSettingLayout header={<ProjectSettingHeader title="Members Settings" />}>
<ConfirmProjectMemberRemove
isOpen={Boolean(selectedRemoveMember) || Boolean(selectedInviteRemoveMember)}
onClose={() => {
@ -252,10 +236,6 @@ const MembersSettings: NextPage = () => {
user={user}
onSuccess={() => mutateMembers()}
/>
<div className="flex flex-row gap-2 h-full">
<div className="w-80 pt-8 overflow-y-hidden flex-shrink-0">
<SettingsSidebar />
</div>
<section className={`pr-9 py-8 w-full overflow-y-auto`}>
<div className="flex items-center py-3.5 border-b border-custom-border-200">
<h3 className="text-xl font-medium">Defaults</h3>
@ -444,8 +424,7 @@ const MembersSettings: NextPage = () => {
</div>
)}
</section>
</div>
</ProjectAuthorizationWrapper>
</ProjectSettingLayout>
);
};

View File

@ -1,7 +1,6 @@
import React, { useState } from "react";
import { useRouter } from "next/router";
import Link from "next/link";
import useSWR from "swr";
@ -11,17 +10,16 @@ import { ProjectStateService } from "services/project";
import useProjectDetails from "hooks/use-project-details";
import useUserAuth from "hooks/use-user-auth";
// layouts
import { ProjectAuthorizationWrapper } from "layouts/auth-layout-legacy";
import { ProjectSettingLayout } from "layouts/setting-layout/project-setting-layout";
// components
import { CreateUpdateStateInline, DeleteStateModal, SingleState, StateGroup } from "components/states";
import { SettingsSidebar } from "components/project";
import { ProjectSettingHeader } from "components/headers";
// ui
import { BreadcrumbItem, Breadcrumbs, Loader } from "@plane/ui";
import { Loader } from "@plane/ui";
// icons
import { Plus } from "lucide-react";
// helpers
import { getStatesList, orderStateGroups } from "helpers/state.helper";
import { truncateText } from "helpers/string.helper";
// types
import type { NextPage } from "next";
// fetch-keys
@ -59,26 +57,7 @@ const StatesSettings: NextPage = () => {
onClose={() => setSelectDeleteState(null)}
user={user}
/>
<ProjectAuthorizationWrapper
breadcrumbs={
<Breadcrumbs onBack={() => router.back()}>
<BreadcrumbItem
link={
<Link href={`/${workspaceSlug}/projects/${projectDetails?.id}/issues`}>
<a className={`border-r-2 border-custom-sidebar-border-200 px-3 text-sm `}>
<p className="truncate">{`${truncateText(projectDetails?.name ?? "Project", 32)}`}</p>
</a>
</Link>
}
/>
<BreadcrumbItem title="States Settings" unshrinkTitle />
</Breadcrumbs>
}
>
<div className="flex flex-row gap-2 h-full">
<div className="w-80 pt-8 overflow-y-hidden flex-shrink-0">
<SettingsSidebar />
</div>
<ProjectSettingLayout header={<ProjectSettingHeader title="States Settings" />}>
<div className="pr-9 py-8 gap-10 w-full overflow-y-auto">
<div className="flex items-center py-3.5 border-b border-custom-border-200">
<h3 className="text-xl font-medium">States</h3>
@ -152,8 +131,7 @@ const StatesSettings: NextPage = () => {
)}
</div>
</div>
</div>
</ProjectAuthorizationWrapper>
</ProjectSettingLayout>
</>
);
};

View File

@ -1,57 +1,15 @@
import React from "react";
import { useRouter } from "next/router";
import Link from "next/link";
import useSWR from "swr";
// services
import { WorkspaceService } from "services/workspace.service";
// layouts
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout-legacy";
import { WorkspaceSettingLayout } from "layouts/setting-layout/workspace-setting-layout";
// component
import { SettingsSidebar } from "components/project";
import { WorkspaceSettingHeader } from "components/headers";
// ui
import { BreadcrumbItem, Breadcrumbs, Button } from "@plane/ui";
import { Button } from "@plane/ui";
// types
import type { NextPage } from "next";
// fetch-keys
import { WORKSPACE_DETAILS } from "constants/fetch-keys";
// helper
import { truncateText } from "helpers/string.helper";
// services
const workspaceService = new WorkspaceService();
const BillingSettings: NextPage = () => {
const router = useRouter();
const { workspaceSlug } = router.query;
const { data: activeWorkspace } = useSWR(workspaceSlug ? WORKSPACE_DETAILS(workspaceSlug as string) : null, () =>
workspaceSlug ? workspaceService.getWorkspace(workspaceSlug as string) : null
);
return (
<WorkspaceAuthorizationLayout
breadcrumbs={
<Breadcrumbs onBack={() => router.back()}>
<BreadcrumbItem
link={
<Link href={`/${workspaceSlug}`}>
<a className={`border-r-2 border-custom-sidebar-border-200 px-3 text-sm `}>
<p className="truncate">{`${truncateText(activeWorkspace?.name ?? "Workspace", 32)}`}</p>
</a>
</Link>
}
/>
<BreadcrumbItem title="Billing & Plans Settings" unshrinkTitle />
</Breadcrumbs>
}
>
<div className="flex flex-row gap-2 h-full">
<div className="w-80 pt-8 overflow-y-hidden flex-shrink-0">
<SettingsSidebar />
</div>
const BillingSettings: NextPage = () => (
<WorkspaceSettingLayout header={<WorkspaceSettingHeader title="Billing & Plans Settings" />}>
<section className="pr-9 py-8 w-full overflow-y-auto">
<div>
<div className="flex items-center py-3.5 border-b border-custom-border-200">
@ -68,9 +26,7 @@ const BillingSettings: NextPage = () => {
</div>
</div>
</section>
</div>
</WorkspaceAuthorizationLayout>
);
};
</WorkspaceSettingLayout>
);
export default BillingSettings;

View File

@ -1,65 +1,20 @@
import { useRouter } from "next/router";
import Link from "next/link";
import useSWR from "swr";
// services
import { WorkspaceService } from "services/workspace.service";
// layouts
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout-legacy";
// components
import ExportGuide from "components/exporter/guide";
import { SettingsSidebar } from "components/project";
// ui
import { BreadcrumbItem, Breadcrumbs } from "@plane/ui";
// types
import type { NextPage } from "next";
// fetch-keys
import { WORKSPACE_DETAILS } from "constants/fetch-keys";
// helper
import { truncateText } from "helpers/string.helper";
import { WorkspaceSettingLayout } from "layouts/setting-layout/workspace-setting-layout";
import { WorkspaceSettingHeader } from "components/headers";
// services
const workspaceService = new WorkspaceService();
const ImportExport: NextPage = () => {
const router = useRouter();
const { workspaceSlug } = router.query;
const { data: activeWorkspace } = useSWR(workspaceSlug ? WORKSPACE_DETAILS(workspaceSlug as string) : null, () =>
workspaceSlug ? workspaceService.getWorkspace(workspaceSlug as string) : null
);
return (
<WorkspaceAuthorizationLayout
breadcrumbs={
<Breadcrumbs onBack={() => router.back()}>
<BreadcrumbItem
link={
<Link href={`/${workspaceSlug}`}>
<a className={`border-r-2 border-custom-sidebar-border-200 px-3 text-sm `}>
<p className="truncate">{`${truncateText(activeWorkspace?.name ?? "Workspace", 32)}`}</p>
</a>
</Link>
}
/>
<BreadcrumbItem title="Export Settings" unshrinkTitle />
</Breadcrumbs>
}
>
<div className="flex flex-row gap-2 h-full">
<div className="w-80 pt-8 overflow-y-hidden flex-shrink-0">
<SettingsSidebar />
</div>
const ImportExport: NextPage = () => (
<WorkspaceSettingLayout header={<WorkspaceSettingHeader title="Export Settings" />}>
<div className="pr-9 py-8 w-full overflow-y-auto">
<div className="flex items-center py-3.5 border-b border-custom-border-200">
<h3 className="text-xl font-medium">Exports</h3>
</div>
<ExportGuide />
</div>
</div>
</WorkspaceAuthorizationLayout>
);
};
</WorkspaceSettingLayout>
);
export default ImportExport;

View File

@ -1,65 +1,20 @@
import { useRouter } from "next/router";
import Link from "next/link";
import useSWR from "swr";
// services
import { WorkspaceService } from "services/workspace.service";
// layouts
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout-legacy";
import { WorkspaceSettingLayout } from "layouts/setting-layout/workspace-setting-layout";
// components
import IntegrationGuide from "components/integration/guide";
import { SettingsSidebar } from "components/project";
// ui
import { BreadcrumbItem, Breadcrumbs } from "@plane/ui";
import { WorkspaceSettingHeader } from "components/headers";
// types
import type { NextPage } from "next";
// fetch-keys
import { WORKSPACE_DETAILS } from "constants/fetch-keys";
// helper
import { truncateText } from "helpers/string.helper";
// services
const workspaceService = new WorkspaceService();
const ImportExport: NextPage = () => {
const router = useRouter();
const { workspaceSlug } = router.query;
const { data: activeWorkspace } = useSWR(workspaceSlug ? WORKSPACE_DETAILS(workspaceSlug as string) : null, () =>
workspaceSlug ? workspaceService.getWorkspace(workspaceSlug as string) : null
);
return (
<WorkspaceAuthorizationLayout
breadcrumbs={
<Breadcrumbs onBack={() => router.back()}>
<BreadcrumbItem
link={
<Link href={`/${workspaceSlug}`}>
<a className={`border-r-2 border-custom-sidebar-border-200 px-3 text-sm `}>
<p className="truncate">{`${truncateText(activeWorkspace?.name ?? "Workspace", 32)}`}</p>
</a>
</Link>
}
/>
<BreadcrumbItem title="Import/ Export Settings" unshrinkTitle />
</Breadcrumbs>
}
>
<div className="flex flex-row gap-2 h-full">
<div className="w-80 pt-8 overflow-y-hidden flex-shrink-0">
<SettingsSidebar />
</div>
const ImportExport: NextPage = () => (
<WorkspaceSettingLayout header={<WorkspaceSettingHeader title="Import Settings" />}>
<section className="pr-9 py-8 w-full overflow-y-auto">
<div className="flex items-center py-3.5 border-b border-custom-border-200">
<h3 className="text-xl font-medium">Imports</h3>
</div>
<IntegrationGuide />
</section>
</div>
</WorkspaceAuthorizationLayout>
);
};
</WorkspaceSettingLayout>
);
export default ImportExport;

View File

@ -13,19 +13,17 @@ import { FileService } from "services/file.service";
import useToast from "hooks/use-toast";
import useUserAuth from "hooks/use-user-auth";
// layouts
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout-legacy";
import { WorkspaceSettingLayout } from "layouts/setting-layout/workspace-setting-layout";
// components
import { ImageUploadModal } from "components/core";
import { DeleteWorkspaceModal } from "components/workspace";
import { SettingsSidebar } from "components/project";
import { WorkspaceSettingHeader } from "components/headers";
// ui
import { Disclosure, Transition } from "@headlessui/react";
import { CustomSelect } from "components/ui";
import { BreadcrumbItem, Breadcrumbs, Button, Input, Spinner } from "@plane/ui";
import { Button, Input, Spinner } from "@plane/ui";
// icons
import { ChevronDown, ChevronUp, Pencil } from "lucide-react";
// helpers
import { truncateText } from "helpers/string.helper";
// types
import type { IWorkspace } from "types";
import type { NextPage } from "next";
@ -68,7 +66,6 @@ const WorkspaceSettings: NextPage = () => {
);
const {
register,
handleSubmit,
control,
reset,
@ -156,13 +153,7 @@ const WorkspaceSettings: NextPage = () => {
const isAdmin = memberDetails?.role === 20;
return (
<WorkspaceAuthorizationLayout
breadcrumbs={
<Breadcrumbs onBack={() => router.back()}>
<BreadcrumbItem title={`${truncateText(activeWorkspace?.name ?? "Workspace", 32)} Settings`} />
</Breadcrumbs>
}
>
<WorkspaceSettingLayout header={<WorkspaceSettingHeader title="General Settings" />}>
<ImageUploadModal
isOpen={isImageUploadModalOpen}
onClose={() => setIsImageUploadModalOpen(false)}
@ -184,10 +175,6 @@ const WorkspaceSettings: NextPage = () => {
data={activeWorkspace ?? null}
user={user}
/>
<div className="flex flex-row gap-2 h-full">
<div className="w-80 pt-8 overflow-y-hidden flex-shrink-0">
<SettingsSidebar />
</div>
{activeWorkspace ? (
<div className={`pr-9 py-8 w-full overflow-y-auto ${isAdmin ? "" : "opacity-60"}`}>
<div className="flex gap-5 items-center pb-7 border-b border-custom-border-200">
@ -292,7 +279,7 @@ const WorkspaceSettings: NextPage = () => {
<Controller
control={control}
name="url"
render={({ field: { value, onChange, ref } }) => (
render={({ field: { onChange, ref } }) => (
<Input
id="url"
name="url"
@ -366,8 +353,7 @@ const WorkspaceSettings: NextPage = () => {
<Spinner />
</div>
)}
</div>
</WorkspaceAuthorizationLayout>
</WorkspaceSettingLayout>
);
};

View File

@ -1,65 +1,38 @@
import React from "react";
import { useRouter } from "next/router";
import Link from "next/link";
import useSWR from "swr";
// services
import { WorkspaceService } from "services/workspace.service";
import { IntegrationService } from "services/integrations";
// layouts
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout-legacy";
import { WorkspaceSettingLayout } from "layouts/setting-layout/workspace-setting-layout";
// components
import { SingleIntegrationCard } from "components/integration";
import { SettingsSidebar } from "components/project";
import { WorkspaceSettingHeader } from "components/headers";
import { Loader } from "@plane/ui";
// ui
import { IntegrationAndImportExportBanner } from "components/ui";
import { BreadcrumbItem, Breadcrumbs, Loader } from "@plane/ui";
// types
import type { NextPage } from "next";
// fetch-keys
import { WORKSPACE_DETAILS, APP_INTEGRATIONS } from "constants/fetch-keys";
import { APP_INTEGRATIONS } from "constants/fetch-keys";
// helper
import { truncateText } from "helpers/string.helper";
// services
const workspaceService = new WorkspaceService();
const integrationService = new IntegrationService();
const WorkspaceIntegrations: NextPage = () => {
const router = useRouter();
const { workspaceSlug } = router.query;
const { data: activeWorkspace } = useSWR(workspaceSlug ? WORKSPACE_DETAILS(workspaceSlug as string) : null, () =>
workspaceSlug ? workspaceService.getWorkspace(workspaceSlug as string) : null
);
const { data: appIntegrations } = useSWR(workspaceSlug ? APP_INTEGRATIONS : null, () =>
workspaceSlug ? integrationService.getAppIntegrationsList() : null
);
return (
<WorkspaceAuthorizationLayout
breadcrumbs={
<Breadcrumbs onBack={() => router.back()}>
<BreadcrumbItem
link={
<Link href={`/${workspaceSlug}`}>
<a className={`border-r-2 border-custom-sidebar-border-200 px-3 text-sm `}>
<p className="truncate">{`${truncateText(activeWorkspace?.name ?? "Workspace", 32)}`}</p>
</a>
</Link>
}
/>
<BreadcrumbItem title="Integrations Settings" unshrinkTitle />
</Breadcrumbs>
}
>
<div className="flex flex-row gap-2 h-full">
<div className="w-80 pt-8 overflow-y-hidden flex-shrink-0">
<SettingsSidebar />
</div>
<WorkspaceSettingLayout header={<WorkspaceSettingHeader title="Export Settings" />}>
<section className="pr-9 py-8 w-full overflow-y-auto">
<IntegrationAndImportExportBanner bannerName="Integrations" />
<div>
@ -75,8 +48,7 @@ const WorkspaceIntegrations: NextPage = () => {
)}
</div>
</section>
</div>
</WorkspaceAuthorizationLayout>
</WorkspaceSettingLayout>
);
};

View File

@ -12,24 +12,23 @@ import useToast from "hooks/use-toast";
import useUser from "hooks/use-user";
import useWorkspaceMembers from "hooks/use-workspace-members";
// layouts
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout-legacy";
import { WorkspaceSettingLayout } from "layouts/setting-layout/workspace-setting-layout";
// components
import ConfirmWorkspaceMemberRemove from "components/workspace/confirm-workspace-member-remove";
import SendWorkspaceInvitationModal from "components/workspace/send-workspace-invitation-modal";
import { SettingsSidebar } from "components/project";
import { WorkspaceSettingHeader } from "components/headers";
// ui
import { BreadcrumbItem, Breadcrumbs, Button, Loader } from "@plane/ui";
import { Button, Loader } from "@plane/ui";
import { CustomMenu, CustomSelect } from "components/ui";
// icons
import { ChevronDown, X } from "lucide-react";
// types
import type { NextPage } from "next";
// fetch-keys
import { WORKSPACE_DETAILS, WORKSPACE_INVITATION_WITH_EMAIL, WORKSPACE_MEMBERS_WITH_EMAIL } from "constants/fetch-keys";
import { WORKSPACE_INVITATION_WITH_EMAIL, WORKSPACE_MEMBERS_WITH_EMAIL } from "constants/fetch-keys";
// constants
import { ROLE } from "constants/workspace";
// helper
import { truncateText } from "helpers/string.helper";
// services
const workspaceService = new WorkspaceService();
@ -48,10 +47,6 @@ const MembersSettings: NextPage = () => {
const { isOwner } = useWorkspaceMembers(workspaceSlug?.toString(), Boolean(workspaceSlug));
const { data: activeWorkspace } = useSWR(workspaceSlug ? WORKSPACE_DETAILS(workspaceSlug.toString()) : null, () =>
workspaceSlug ? workspaceService.getWorkspace(workspaceSlug.toString()) : null
);
const { data: workspaceMembers, mutate: mutateMembers } = useSWR(
workspaceSlug ? WORKSPACE_MEMBERS_WITH_EMAIL(workspaceSlug.toString()) : null,
workspaceSlug ? () => workspaceService.workspaceMembersWithEmail(workspaceSlug.toString()) : null
@ -98,22 +93,7 @@ const MembersSettings: NextPage = () => {
};
return (
<WorkspaceAuthorizationLayout
breadcrumbs={
<Breadcrumbs onBack={() => router.back()}>
<BreadcrumbItem
link={
<Link href={`/${workspaceSlug}`}>
<a className={`border-r-2 border-custom-sidebar-border-200 px-3 text-sm `}>
<p className="truncate">{`${truncateText(activeWorkspace?.name ?? "Workspace", 32)}`}</p>
</a>
</Link>
}
/>
<BreadcrumbItem title="Members Settings" unshrinkTitle />
</Breadcrumbs>
}
>
<WorkspaceSettingLayout header={<WorkspaceSettingHeader title="Members Settings" />}>
<ConfirmWorkspaceMemberRemove
isOpen={Boolean(selectedRemoveMember) || Boolean(selectedInviteRemoveMember)}
onClose={() => {
@ -181,14 +161,10 @@ const MembersSettings: NextPage = () => {
user={user}
onSuccess={handleInviteModalSuccess}
/>
<div className="flex flex-row gap-2 h-full">
<div className="w-80 pt-8 overflow-y-hidden flex-shrink-0">
<SettingsSidebar />
</div>
<section className="pr-9 py-8 w-full overflow-y-auto">
<div className="flex items-center justify-between gap-4 pt-2 pb-3.5 border-b border-custom-border-200">
<div className="flex items-center justify-between gap-4 py-3.5 border-b border-custom-border-200">
<h4 className="text-xl font-medium">Members</h4>
<Button variant="primary" onClick={() => setInviteModal(true)}>
<Button variant="primary" size="sm" onClick={() => setInviteModal(true)}>
Add Member
</Button>
</div>
@ -328,8 +304,7 @@ const MembersSettings: NextPage = () => {
</div>
)}
</section>
</div>
</WorkspaceAuthorizationLayout>
</WorkspaceSettingLayout>
);
};