diff --git a/web/components/common/empty-state.tsx b/web/components/common/empty-state.tsx index 6cccf614e..149d76540 100644 --- a/web/components/common/empty-state.tsx +++ b/web/components/common/empty-state.tsx @@ -15,7 +15,6 @@ type Props = { onClick: () => void; }; secondaryButton?: React.ReactNode; - isFullScreen?: boolean; disabled?: boolean; }; @@ -25,10 +24,9 @@ export const EmptyState: React.FC = ({ image, primaryButton, secondaryButton, - isFullScreen = true, disabled = false, }) => ( -
+
{primaryButton?.text}
{title}
diff --git a/web/components/headers/index.ts b/web/components/headers/index.ts index abb782f09..c172fdb7a 100644 --- a/web/components/headers/index.ts +++ b/web/components/headers/index.ts @@ -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"; diff --git a/web/components/headers/project-settings.tsx b/web/components/headers/project-settings.tsx new file mode 100644 index 000000000..e2891de4c --- /dev/null +++ b/web/components/headers/project-settings.tsx @@ -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 = 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 ( +
+
+
+ +
+ +
+
+ ); +}); diff --git a/web/components/headers/workspace-settings.tsx b/web/components/headers/workspace-settings.tsx new file mode 100644 index 000000000..a96c20d0c --- /dev/null +++ b/web/components/headers/workspace-settings.tsx @@ -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 = 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 ( +
+
+
+ +
+ +
+
+ ); +}); diff --git a/web/components/notifications/notification-popover.tsx b/web/components/notifications/notification-popover.tsx index bcdb0c9f1..a8d5d1587 100644 --- a/web/components/notifications/notification-popover.tsx +++ b/web/components/notifications/notification-popover.tsx @@ -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} />
) diff --git a/web/components/project/delete-project-section.tsx b/web/components/project/delete-project-section.tsx new file mode 100644 index 000000000..ccd0bf571 --- /dev/null +++ b/web/components/project/delete-project-section.tsx @@ -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 = (props) => { + const { projectDetails, handleDelete } = props; + return ( + + {({ open }) => ( +
+ + Delete Project + {open ? : } + + + + +
+ + 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. + +
+ {projectDetails ? ( +
+ +
+ ) : ( + + + + )} +
+
+
+
+
+ )} +
+ ); +}; diff --git a/web/components/project/index.ts b/web/components/project/index.ts index e702de8ad..9191a88bc 100644 --- a/web/components/project/index.ts +++ b/web/components/project/index.ts @@ -14,3 +14,4 @@ export * from "./card"; export * from "./join-project-modal"; export * from "./form"; export * from "./form-loader"; +export * from "./delete-project-section"; diff --git a/web/layouts/setting-layout/project-setting-layout/index.tsx b/web/layouts/setting-layout/project-setting-layout/index.tsx new file mode 100644 index 000000000..c4bfd4db3 --- /dev/null +++ b/web/layouts/setting-layout/project-setting-layout/index.tsx @@ -0,0 +1,2 @@ +export * from "./layout"; +export * from "./sidebar"; diff --git a/web/layouts/setting-layout/project-setting-layout/layout.tsx b/web/layouts/setting-layout/project-setting-layout/layout.tsx new file mode 100644 index 000000000..4a5355a97 --- /dev/null +++ b/web/layouts/setting-layout/project-setting-layout/layout.tsx @@ -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 = (props) => { + const { children, header } = props; + + return ( + <> + + +
+ +
+ {header} +
+
+
+ +
+ {children} +
+
+
+
+
+
+ + ); +}; diff --git a/web/layouts/setting-layout/project-setting-layout/sidebar.tsx b/web/layouts/setting-layout/project-setting-layout/sidebar.tsx new file mode 100644 index 000000000..2ce97dd88 --- /dev/null +++ b/web/layouts/setting-layout/project-setting-layout/sidebar.tsx @@ -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 ( +
+
+ SETTINGS +
+ {projectLinks.map((link) => ( + + +
+ {link.label} +
+
+ + ))} +
+
+
+ ); +}; diff --git a/web/layouts/setting-layout/workspace-setting-layout/index.tsx b/web/layouts/setting-layout/workspace-setting-layout/index.tsx new file mode 100644 index 000000000..c4bfd4db3 --- /dev/null +++ b/web/layouts/setting-layout/workspace-setting-layout/index.tsx @@ -0,0 +1,2 @@ +export * from "./layout"; +export * from "./sidebar"; diff --git a/web/layouts/setting-layout/workspace-setting-layout/layout.tsx b/web/layouts/setting-layout/workspace-setting-layout/layout.tsx new file mode 100644 index 000000000..74c09b05f --- /dev/null +++ b/web/layouts/setting-layout/workspace-setting-layout/layout.tsx @@ -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 = (props) => { + const { children, header } = props; + + return ( + <> + + +
+ +
+ {header} +
+
+
+ +
+ {children} +
+
+
+
+
+
+ + ); +}; diff --git a/web/layouts/setting-layout/workspace-setting-layout/sidebar.tsx b/web/layouts/setting-layout/workspace-setting-layout/sidebar.tsx new file mode 100644 index 000000000..caf4f8358 --- /dev/null +++ b/web/layouts/setting-layout/workspace-setting-layout/sidebar.tsx @@ -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 ( +
+
+ SETTINGS +
+ {workspaceLinks.map((link) => ( + + +
+ {link.label} +
+
+ + ))} +
+
+
+ My Account +
+ {profileLinks.map((link) => ( + + +
+ {link.label} +
+
+ + ))} +
+
+
+ ); +}; diff --git a/web/pages/[workspaceSlug]/me/profile/activity.tsx b/web/pages/[workspaceSlug]/me/profile/activity.tsx index 9d58e7bc9..5298da83f 100644 --- a/web/pages/[workspaceSlug]/me/profile/activity.tsx +++ b/web/pages/[workspaceSlug]/me/profile/activity.tsx @@ -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,174 +30,160 @@ const ProfileActivity = () => { ); return ( - router.back()}> - - - } - > -
-
- -
+ }> + {userActivity ? ( +
+
+

Activity

+
+
+
    + {userActivity.results.map((activityItem: any) => { + if (activityItem.field === "comment") { + return ( +
    +
    +
    + {activityItem.field ? ( + activityItem.new_value === "restore" && ( + + ) + ) : activityItem.actor_detail.avatar && activityItem.actor_detail.avatar !== "" ? ( + {activityItem.actor_detail.display_name} + ) : ( +
    + {activityItem.actor_detail.display_name?.charAt(0)} +
    + )} - {userActivity ? ( -
    -
    -

    Activity

    -
    -
    -
      - {userActivity.results.map((activityItem: any) => { - if (activityItem.field === "comment") { - return ( -
      -
      -
      - {activityItem.field ? ( - activityItem.new_value === "restore" && ( - - ) - ) : activityItem.actor_detail.avatar && activityItem.actor_detail.avatar !== "" ? ( - {activityItem.actor_detail.display_name} - ) : ( -
      - {activityItem.actor_detail.display_name?.charAt(0)} -
      - )} - - - + + +
      +
      +
      +
      + {activityItem.actor_detail.is_bot + ? activityItem.actor_detail.first_name + " Bot" + : activityItem.actor_detail.display_name} +
      +

      + Commented {timeAgo(activityItem.created_at)} +

      -
      -
      -
      - {activityItem.actor_detail.is_bot - ? activityItem.actor_detail.first_name + " Bot" - : activityItem.actor_detail.display_name} -
      -

      - Commented {timeAgo(activityItem.created_at)} -

      -
      -
      - -
      +
      +
      - ); - } +
      + ); + } - const message = - activityItem.verb === "created" && - activityItem.field !== "cycles" && - activityItem.field !== "modules" && - activityItem.field !== "attachment" && - activityItem.field !== "link" && - activityItem.field !== "estimate" ? ( - - created{" "} - - - this issue. - - - - ) : activityItem.field ? ( - - ) : ( - "created the issue." - ); + const message = + activityItem.verb === "created" && + activityItem.field !== "cycles" && + activityItem.field !== "modules" && + activityItem.field !== "attachment" && + activityItem.field !== "link" && + activityItem.field !== "estimate" ? ( + + created{" "} + + + this issue. + + + + ) : activityItem.field ? ( + + ) : ( + "created the issue." + ); - if ("field" in activityItem && activityItem.field !== "updated_by") { - return ( -
    • -
      -
      - <> -
      -
      -
      -
      - {activityItem.field ? ( - activityItem.new_value === "restore" ? ( - - ) : ( - - ) - ) : activityItem.actor_detail.avatar && - activityItem.actor_detail.avatar !== "" ? ( - {activityItem.actor_detail.display_name} + if ("field" in activityItem && activityItem.field !== "updated_by") { + return ( +
    • +
      +
      + <> +
      +
      +
      +
      + {activityItem.field ? ( + activityItem.new_value === "restore" ? ( + ) : ( -
      - {activityItem.actor_detail.display_name?.charAt(0)} -
      - )} -
      + + ) + ) : activityItem.actor_detail.avatar && activityItem.actor_detail.avatar !== "" ? ( + {activityItem.actor_detail.display_name} + ) : ( +
      + {activityItem.actor_detail.display_name?.charAt(0)} +
      + )}
      -
      -
      - {activityItem.field === "archived_at" && activityItem.new_value !== "restore" ? ( - Plane - ) : activityItem.actor_detail.is_bot ? ( - - {activityItem.actor_detail.first_name} Bot - - ) : ( - - {activityItem.actor_detail.display_name} - - )}{" "} - {message}{" "} - {timeAgo(activityItem.created_at)} -
      +
      +
      +
      + {activityItem.field === "archived_at" && activityItem.new_value !== "restore" ? ( + Plane + ) : activityItem.actor_detail.is_bot ? ( + + {activityItem.actor_detail.first_name} Bot + + ) : ( + + {activityItem.actor_detail.display_name} + + )}{" "} + {message} {timeAgo(activityItem.created_at)}
      - -
      +
      +
      -
    • - ); - } - })} -
    -
    -
    - ) : ( - - - - - - - )} -
    - +
    + + ); + } + })} +
+
+
+ ) : ( + + + + + + + )} +
); }; diff --git a/web/pages/[workspaceSlug]/me/profile/index.tsx b/web/pages/[workspaceSlug]/me/profile/index.tsx index d4dbc4c53..3f6ac3a67 100644 --- a/web/pages/[workspaceSlug]/me/profile/index.tsx +++ b/web/pages/[workspaceSlug]/me/profile/index.tsx @@ -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 ( - router.back()}> - - - } - > + }> setIsImageUploadModalOpen(false)} @@ -166,231 +160,226 @@ const Profile: NextPage = () => { /> {myProfile ? (
-
-
- -
-
-
- {myProfile?.first_name +
+ {myProfile?.first_name +
+
+
+ +
+
+
+ +
+ ( + { + setValue("cover_image", imageUrl); + }} + control={control} + value={watch("cover_image") ?? "https://images.unsplash.com/photo-1506383796573-caf02b4a79ab"} + /> + )} /> -
-
-
- -
-
-
+
+
-
- ( - { - setValue("cover_image", imageUrl); - }} - control={control} - value={watch("cover_image") ?? "https://images.unsplash.com/photo-1506383796573-caf02b4a79ab"} - /> - )} - /> +
+
+
+ {`${watch("first_name")} ${watch("last_name")}`}
+ {watch("email")}
-
-
-
- {`${watch("first_name")} ${watch("last_name")}`} -
- {watch("email")} -
+ + + + + + View Profile + + +
- - - - - - View Profile - - +
+
+

First Name

+ ( + + )} + />
-
-
-

First Name

- ( - - )} - /> -
+
+

Last Name

-
-

Last Name

+ ( + + )} + /> +
- ( - - )} - /> -
+
+

Email

+ ( + + )} + /> +
-
-

Email

- ( - - )} - /> -
+
+

Role

+ ( + + {USER_ROLES.map((item) => ( + + {item.label} + + ))} + + )} + /> + {errors.role && Please select a role} +
-
-

Role

- ( - - {USER_ROLES.map((item) => ( - - {item.label} - - ))} - - )} - /> - {errors.role && Please select a role} -
+
+

Display name

+ { + if (value.trim().length < 1) return "Display name can't be empty."; -
-

Display name

- { - if (value.trim().length < 1) return "Display name can't be empty."; + if (value.split(" ").length > 1) return "Display name can't have two consecutive spaces."; - if (value.split(" ").length > 1) return "Display name can't have two consecutive spaces."; + if (value.replace(/\s/g, "").length < 1) + return "Display name must be at least 1 characters long."; - if (value.replace(/\s/g, "").length < 1) - return "Display name must be at least 1 characters long."; + if (value.replace(/\s/g, "").length > 20) + return "Display name must be less than 20 characters long."; - if (value.replace(/\s/g, "").length > 20) - return "Display name must be less than 20 characters long."; + return true; + }, + }} + render={({ field: { value, onChange, ref } }) => ( + + )} + /> +
- return true; - }, - }} - render={({ field: { value, onChange, ref } }) => ( - - )} - /> -
+
+

Timezone

-
-

Timezone

+ ( + t.value === value)?.label ?? value : "Select a timezone"} + options={timeZoneOptions} + onChange={onChange} + optionsClassName="w-full" + input + /> + )} + /> + {errors.role && Please select a role} +
- ( - t.value === value)?.label ?? value : "Select a timezone"} - options={timeZoneOptions} - onChange={onChange} - optionsClassName="w-full" - input - /> - )} - /> - {errors.role && Please select a role} -
- -
- -
+
+
@@ -400,7 +389,7 @@ const Profile: NextPage = () => {
)} - + ); }; diff --git a/web/pages/[workspaceSlug]/me/profile/preferences.tsx b/web/pages/[workspaceSlug]/me/profile/preferences.tsx index d08a370cd..beae8a341 100644 --- a/web/pages/[workspaceSlug]/me/profile/preferences.tsx +++ b/web/pages/[workspaceSlug]/me/profile/preferences.tsx @@ -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,37 +44,29 @@ const ProfilePreferencesPage = observer(() => { }; return ( - }> - <> - {userStore.currentUser ? ( -
-
- + }> + {userStore.currentUser ? ( +
+
+

Preferences

+
+
+
+

Theme

+

Select or customize your interface color scheme.

- -
-
-

Preferences

-
-
-
-

Theme

-

Select or customize your interface color scheme.

-
-
- -
-
- {userTheme?.theme === "custom" && } +
+
- ) : ( -
- -
- )} - - + {userTheme?.theme === "custom" && } +
+ ) : ( +
+ +
+ )} + ); }); diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/settings/automations.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/settings/automations.tsx index 74df52f6a..cc709d598 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/settings/automations.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/settings/automations.tsx @@ -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,35 +70,15 @@ const AutomationsSettings: NextPage = () => { const isAdmin = memberDetails?.role === 20; return ( - router.back()}> - - -

{`${truncateText(projectDetails?.name ?? "Project", 32)}`}

-
- - } - /> - - - } - > -
-
- + }> +
+
+

Automations

-
-
-

Automations

-
- - -
-
- + + + + ); }; diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/settings/estimates.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/settings/estimates.tsx index 997cbf719..125142cf2 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/settings/estimates.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/settings/estimates.tsx @@ -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,89 +108,70 @@ const EstimatesSettings: NextPage = () => { }} user={user} /> - router.back()}> - - -

{`${truncateText(projectDetails?.name ?? "Project", 32)}`}

-
- - } - /> - - - } - > -
-
- -
-
-
-

Estimates

-
-
- + {projectDetails?.estimate && ( + + )} +
+
+
+ {estimatesList ? ( + estimatesList.length > 0 ? ( +
+ {estimatesList.map((estimate) => ( + editEstimate(estimate)} + handleEstimateDelete={(estimateId) => removeEstimate(estimateId)} + user={user} + /> + ))} +
+ ) : ( +
+ , + text: "Add Estimate", + onClick: () => { setEstimateToUpdate(undefined); setEstimateFormOpen(true); - }} - > - Add Estimate - - {projectDetails?.estimate && ( - - )} -
+ }, + }} + />
- - {estimatesList ? ( - estimatesList.length > 0 ? ( -
- {estimatesList.map((estimate) => ( - editEstimate(estimate)} - handleEstimateDelete={(estimateId) => removeEstimate(estimateId)} - user={user} - /> - ))} -
- ) : ( -
- , - text: "Add Estimate", - onClick: () => { - setEstimateToUpdate(undefined); - setEstimateFormOpen(true); - }, - }} - /> -
- ) - ) : ( - - - - - - - )} -
+ ) + ) : ( + + + + + + + )}
-
+ ); }; diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/settings/features.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/settings/features.tsx index 1dc80ac85..2e232e1b0 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/settings/features.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/settings/features.tsx @@ -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,72 +136,52 @@ const FeaturesSettings: NextPage = () => { const isAdmin = memberDetails?.role === 20; return ( - router.back()}> - - -

{`${truncateText(projectDetails?.name ?? "Project", 32)}`}

-
- - } - /> - - - } - > -
-
- + }> +
+
+

Features

-
-
-

Features

-
-
- {featuresList.map((feature) => ( -
-
-
- {feature.icon} -
-
-

{feature.title}

-

{feature.description}

-
+
+ {featuresList.map((feature) => ( +
+
+
+ {feature.icon} +
+
+

{feature.title}

+

{feature.description}

- { - trackEventService.trackMiscellaneousEvent( - { - workspaceId: (projectDetails?.workspace as any)?.id, - workspaceSlug, - projectId, - projectIdentifier: projectDetails?.identifier, - projectName: projectDetails?.name, - }, - getEventType(feature.title, !projectDetails?.[feature.property as keyof IProject]), - user as IUser - ); - handleSubmit({ - [feature.property]: !projectDetails?.[feature.property as keyof IProject], - }); - }} - disabled={!isAdmin} - size="sm" - />
- ))} -
-
-
- + { + trackEventService.trackMiscellaneousEvent( + { + workspaceId: (projectDetails?.workspace as any)?.id, + workspaceSlug, + projectId, + projectIdentifier: projectDetails?.identifier, + projectName: projectDetails?.name, + }, + getEventType(feature.title, !projectDetails?.[feature.property as keyof IProject]), + user as IUser + ); + handleSubmit({ + [feature.property]: !projectDetails?.[feature.property as keyof IProject], + }); + }} + disabled={!isAdmin} + size="sm" + /> +
+ ))} +
+ + ); }; diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/settings/index.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/settings/index.tsx index e85a66c85..26b3ae90c 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/settings/index.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/settings/index.tsx @@ -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 ( - router.back()}> - - -

{`${truncateText(projectDetails?.name ?? "Project", 32)}`}

-
- - } - /> - - - } - > + }> {projectDetails && ( { /> )} -
-
- -
-
- {projectDetails && workspaceSlug ? ( - - ) : ( - - )} +
+ {projectDetails && workspaceSlug ? ( + + ) : ( + + )} - {isAdmin && ( - - {({ open }) => ( -
- - Delete Project - {open ? : } - - - - -
- - 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. - -
- {projectDetails ? ( -
- -
- ) : ( - - - - )} -
-
-
-
-
- )} -
- )} -
+ {isAdmin && ( + setSelectedProject(projectDetails.id ?? null)} + /> + )}
- + ); }); diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/settings/integrations.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/settings/integrations.tsx index e989dcbfa..7daa2cec1 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/settings/integrations.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/settings/integrations.tsx @@ -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( workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null, workspaceSlug && projectId ? () => projectService.getProject(workspaceSlug as string, projectId as string) : null @@ -43,70 +47,43 @@ 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 ( - router.back()}> - - -

{`${truncateText(projectDetails?.name ?? "Project", 32)}`}

-
- - } - /> - - - } - > -
-
- + }> +
+
+

Integrations

-
-
-

Integrations

-
- {workspaceIntegrations ? ( - workspaceIntegrations.length > 0 ? ( -
- {workspaceIntegrations.map((integration) => ( - - ))} -
- ) : ( - router.push(`/${workspaceSlug}/settings/integrations`), - }} - disabled={!isAdmin} - /> - ) + {workspaceIntegrations ? ( + workspaceIntegrations.length > 0 ? ( +
+ {workspaceIntegrations.map((integration) => ( + + ))} +
) : ( - - - - - - - )} -
+ router.push(`/${workspaceSlug}/settings/integrations`), + }} + disabled={!isAdmin} + /> + ) + ) : ( + + + + + + + )}
- +
); }; diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/settings/labels.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/settings/labels.tsx index 68ac28503..222cbc90a 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/settings/labels.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/settings/labels.tsx @@ -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(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,78 +92,43 @@ const LabelsSettings: NextPage = () => { onClose={() => setSelectDeleteLabel(null)} user={user} /> - router.back()}> - - -

{`${truncateText(projectDetails?.name ?? "Project", 32)}`}

-
- - } - /> - - - } - > -
-
- + }> +
+
+

Labels

+ +
-
-
-

Labels

+
+ {labelForm && ( + { + setLabelForm(false); + setIsUpdating(false); + setLabelToUpdate(null); + }} + ref={scrollToRef} + /> + )} + <> + {issueLabels ? ( + issueLabels.length > 0 ? ( + issueLabels.map((label) => { + const children = issueLabels?.filter((l) => l.parent === label.id); - -
-
- {labelForm && ( - { - setLabelForm(false); - setIsUpdating(false); - setLabelToUpdate(null); - }} - ref={scrollToRef} - /> - )} - <> - {issueLabels ? ( - issueLabels.length > 0 ? ( - issueLabels.map((label) => { - const children = issueLabels?.filter((l) => l.parent === label.id); - - if (children && children.length === 0) { - if (!label.parent) - return ( - addLabelToGroup(label)} - editLabel={(label) => { - editLabel(label); - scrollToRef.current?.scrollIntoView({ - behavior: "smooth", - }); - }} - handleLabelDelete={() => setSelectDeleteLabel(label)} - /> - ); - } else + if (children && children.length === 0) { + if (!label.parent) return ( - addLabelToGroup(label)} editLabel={(label) => { editLabel(label); scrollToRef.current?.scrollIntoView({ @@ -181,35 +136,49 @@ const LabelsSettings: NextPage = () => { }); }} handleLabelDelete={() => setSelectDeleteLabel(label)} - user={user} /> ); - }) - ) : ( - newLabel(), - }} - isFullScreen={false} - /> - ) + } else + return ( + { + editLabel(label); + scrollToRef.current?.scrollIntoView({ + behavior: "smooth", + }); + }} + handleLabelDelete={() => setSelectDeleteLabel(label)} + user={user} + /> + ); + }) ) : ( - - - - - - - )} - -
-
-
- + newLabel(), + }} + /> + ) + ) : ( + + + + + + + )} + +
+ + ); }; diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/settings/members.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/settings/members.tsx index d91e6084d..f6556fd26 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/settings/members.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/settings/members.tsx @@ -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 = { project_lead: null, @@ -198,22 +197,7 @@ const MembersSettings: NextPage = () => { const isAdmin = memberDetails?.role === 20; return ( - router.back()}> - - -

{`${truncateText(projectDetails?.name ?? "Project", 32)}`}

-
- - } - /> - - - } - > + }> { @@ -252,200 +236,195 @@ const MembersSettings: NextPage = () => { user={user} onSuccess={() => mutateMembers()} /> -
-
- +
+
+

Defaults

-
-
-

Defaults

-
-
-
-
-

Project Lead

-
- {projectDetails ? ( - ( - { - submitChanges({ project_lead: val }); - }} - isDisabled={!isAdmin} - /> - )} - /> - ) : ( - - - - )} -
+
+
+
+

Project Lead

+
+ {projectDetails ? ( + ( + { + submitChanges({ project_lead: val }); + }} + isDisabled={!isAdmin} + /> + )} + /> + ) : ( + + + + )}
+
-
-

Default Assignee

-
- {projectDetails ? ( - ( - { - submitChanges({ default_assignee: val }); - }} - isDisabled={!isAdmin} - /> - )} - /> - ) : ( - - - - )} -
+
+

Default Assignee

+
+ {projectDetails ? ( + ( + { + submitChanges({ default_assignee: val }); + }} + isDisabled={!isAdmin} + /> + )} + /> + ) : ( + + + + )}
+
-
-

Members

- -
- {!projectMembers || !projectInvitations ? ( - - - - - - - ) : ( -
- {members.length > 0 - ? members.map((member) => ( -
-
- {member.avatar && member.avatar !== "" ? ( -
- {member.display_name} -
- ) : member.display_name || member.email ? ( -
- {(member.display_name || member.email)?.charAt(0)} -
- ) : ( -
- ? -
- )} -
- {member.member ? ( - - - - {member.first_name} {member.last_name} - - ({member.display_name}) - - - ) : ( -

{member.display_name || member.email}

- )} - {isOwner &&

{member.email}

} +
+

Members

+ +
+ {!projectMembers || !projectInvitations ? ( + + + + + + + ) : ( +
+ {members.length > 0 + ? members.map((member) => ( +
+
+ {member.avatar && member.avatar !== "" ? ( +
+ {member.display_name}
-
-
- {!member.member && ( -
- Pending -
- )} - - - {ROLE[member.role as keyof typeof ROLE]} + ) : member.display_name || member.email ? ( +
+ {(member.display_name || member.email)?.charAt(0)} +
+ ) : ( +
+ ? +
+ )} + - } - value={member.role} - onChange={(value: 5 | 10 | 15 | 20 | undefined) => { - if (!activeWorkspace || !projectDetails) return; - - mutateMembers( - (prevData: any) => - prevData.map((m: any) => (m.id === member.id ? { ...m, role: value } : m)), - false - ); - - projectService - .updateProjectMember(activeWorkspace.slug, projectDetails.id, member.id, { - role: value, - }) - .catch(() => { - setToastAlert({ - type: "error", - title: "Error!", - message: "An error occurred while updating member role. Please try again.", - }); - }); - }} - disabled={ - member.memberId === user?.id || - !member.member || - (currentUser && currentUser.role !== 20 && currentUser.role < member.role) - } - > - {Object.keys(ROLE).map((key) => { - if (currentUser && currentUser.role !== 20 && currentUser.role < parseInt(key)) return null; - - return ( - - <>{ROLE[parseInt(key) as keyof typeof ROLE]} - - ); - })} -
- - { - if (member.member) setSelectedRemoveMember(member.id); - else setSelectedInviteRemoveMember(member.id); - }} - > - - - - {member.memberId !== user?.id ? "Remove member" : "Leave project"} - - - + ({member.display_name}) + + + ) : ( +

{member.display_name || member.email}

+ )} + {isOwner &&

{member.email}

}
- )) - : null} -
- )} -
-
- +
+ {!member.member && ( +
+ Pending +
+ )} + + + {ROLE[member.role as keyof typeof ROLE]} + + {member.memberId !== user?.id && } +
+ } + value={member.role} + onChange={(value: 5 | 10 | 15 | 20 | undefined) => { + if (!activeWorkspace || !projectDetails) return; + + mutateMembers( + (prevData: any) => + prevData.map((m: any) => (m.id === member.id ? { ...m, role: value } : m)), + false + ); + + projectService + .updateProjectMember(activeWorkspace.slug, projectDetails.id, member.id, { + role: value, + }) + .catch(() => { + setToastAlert({ + type: "error", + title: "Error!", + message: "An error occurred while updating member role. Please try again.", + }); + }); + }} + disabled={ + member.memberId === user?.id || + !member.member || + (currentUser && currentUser.role !== 20 && currentUser.role < member.role) + } + > + {Object.keys(ROLE).map((key) => { + if (currentUser && currentUser.role !== 20 && currentUser.role < parseInt(key)) return null; + + return ( + + <>{ROLE[parseInt(key) as keyof typeof ROLE]} + + ); + })} + + + { + if (member.member) setSelectedRemoveMember(member.id); + else setSelectedInviteRemoveMember(member.id); + }} + > + + + + {member.memberId !== user?.id ? "Remove member" : "Leave project"} + + + +
+
+ )) + : null} +
+ )} + + ); }; diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/settings/states.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/settings/states.tsx index 5e27d9755..af233d013 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/settings/states.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/settings/states.tsx @@ -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,101 +57,81 @@ const StatesSettings: NextPage = () => { onClose={() => setSelectDeleteState(null)} user={user} /> - router.back()}> - - -

{`${truncateText(projectDetails?.name ?? "Project", 32)}`}

-
- - } - /> - - - } - > -
-
- + }> +
+
+

States

-
-
-

States

-
-
- {states && projectDetails && orderedStateGroups ? ( - Object.keys(orderedStateGroups).map((key) => { - if (orderedStateGroups[key].length !== 0) - return ( -
-
-

{key}

- -
-
- {key === activeGroup && ( - { - setActiveGroup(null); - setSelectedState(null); - }} - data={null} - selectedGroup={key as keyof StateGroup} +
+ {states && projectDetails && orderedStateGroups ? ( + Object.keys(orderedStateGroups).map((key) => { + if (orderedStateGroups[key].length !== 0) + return ( +
+
+

{key}

+ +
+
+ {key === activeGroup && ( + { + setActiveGroup(null); + setSelectedState(null); + }} + data={null} + selectedGroup={key as keyof StateGroup} + user={user} + /> + )} + {orderedStateGroups[key].map((state, index) => + state.id !== selectedState ? ( + setSelectedState(state.id)} + handleDeleteState={() => setSelectDeleteState(state.id)} user={user} /> - )} - {orderedStateGroups[key].map((state, index) => - state.id !== selectedState ? ( - setSelectedState(state.id)} - handleDeleteState={() => setSelectDeleteState(state.id)} + ) : ( +
+ { + setActiveGroup(null); + setSelectedState(null); + }} + groupLength={orderedStateGroups[key].length} + data={statesList?.find((state) => state.id === selectedState) ?? null} + selectedGroup={key as keyof StateGroup} user={user} /> - ) : ( -
- { - setActiveGroup(null); - setSelectedState(null); - }} - groupLength={orderedStateGroups[key].length} - data={statesList?.find((state) => state.id === selectedState) ?? null} - selectedGroup={key as keyof StateGroup} - user={user} - /> -
- ) - )} -
+
+ ) + )}
- ); - }) - ) : ( - - - - - - - )} -
+
+ ); + }) + ) : ( + + + + + + + )}
- + ); }; diff --git a/web/pages/[workspaceSlug]/settings/billing.tsx b/web/pages/[workspaceSlug]/settings/billing.tsx index 9d5d241c4..65a645217 100644 --- a/web/pages/[workspaceSlug]/settings/billing.tsx +++ b/web/pages/[workspaceSlug]/settings/billing.tsx @@ -1,76 +1,32 @@ 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 ( - router.back()}> - - -

{`${truncateText(activeWorkspace?.name ?? "Workspace", 32)}`}

-
- - } - /> - - - } - > -
-
- +const BillingSettings: NextPage = () => ( + }> +
+
+
+

Billing & Plans

-
-
-
-

Billing & Plans

-
-
-
-
-

Current plan

-

You are currently using the free plan

- - - -
-
-
- - ); -}; +
+
+

Current plan

+

You are currently using the free plan

+ + + +
+
+
+
+); export default BillingSettings; diff --git a/web/pages/[workspaceSlug]/settings/exports.tsx b/web/pages/[workspaceSlug]/settings/exports.tsx index ec391dc83..97e599094 100644 --- a/web/pages/[workspaceSlug]/settings/exports.tsx +++ b/web/pages/[workspaceSlug]/settings/exports.tsx @@ -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 ( - router.back()}> - - -

{`${truncateText(activeWorkspace?.name ?? "Workspace", 32)}`}

-
- - } - /> - - - } - > -
-
- -
-
-
-

Exports

-
- -
+const ImportExport: NextPage = () => ( + }> +
+
+

Exports

- - ); -}; + +
+
+); export default ImportExport; diff --git a/web/pages/[workspaceSlug]/settings/imports.tsx b/web/pages/[workspaceSlug]/settings/imports.tsx index 27b700cac..5263a97b7 100644 --- a/web/pages/[workspaceSlug]/settings/imports.tsx +++ b/web/pages/[workspaceSlug]/settings/imports.tsx @@ -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 ( - router.back()}> - - -

{`${truncateText(activeWorkspace?.name ?? "Workspace", 32)}`}

-
- - } - /> - - - } - > -
-
- -
-
-
-

Imports

-
- -
+const ImportExport: NextPage = () => ( + }> +
+
+

Imports

- - ); -}; + +
+
+); export default ImportExport; diff --git a/web/pages/[workspaceSlug]/settings/index.tsx b/web/pages/[workspaceSlug]/settings/index.tsx index 1edb6cd52..0f4c4bd66 100644 --- a/web/pages/[workspaceSlug]/settings/index.tsx +++ b/web/pages/[workspaceSlug]/settings/index.tsx @@ -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 ( - router.back()}> - - - } - > + }> setIsImageUploadModalOpen(false)} @@ -184,190 +175,185 @@ const WorkspaceSettings: NextPage = () => { data={activeWorkspace ?? null} user={user} /> -
-
- -
- {activeWorkspace ? ( -
-
-
- +
+
+

{watch("name")}

+ {`${ + typeof window !== "undefined" && window.location.origin.replace("http://", "").replace("https://", "") + }/${activeWorkspace.slug}`} +
+
-
-

{watch("name")}

- {`${ - typeof window !== "undefined" && window.location.origin.replace("http://", "").replace("https://", "") - }/${activeWorkspace.slug}`} -
-
+
+ +
+
+
+

Workspace Name

+ ( + + )} + /> +
+ +
+

Company Size

+ ( + c === value) ?? "Select organization size"} + width="w-full" + input + disabled={!isAdmin} + > + {ORGANIZATION_SIZE?.map((item) => ( + + {item} + + ))} + + )} + /> +
+ +
+

Workspace URL

+ ( + + )} + /> +
+
+ +
+ +
+
+ {isAdmin && ( + + {({ open }) => ( +
+ - {watch("logo") && watch("logo") !== null && watch("logo") !== "" ? ( - <> - - Edit logo - - ) : ( - "Upload logo" - )} - -
-
-
+ Delete Workspace + {/* */} + {open ? : } + -
-
-
-

Workspace Name

- ( - - )} - /> -
- -
-

Company Size

- ( - c === value) ?? "Select organization size"} - width="w-full" - input - disabled={!isAdmin} - > - {ORGANIZATION_SIZE?.map((item) => ( - - {item} - - ))} - - )} - /> -
- -
-

Workspace URL

- ( - - )} - /> -
-
- -
- -
-
- {isAdmin && ( - - {({ open }) => ( -
- - Delete Workspace - {/* */} - {open ? : } - - - - -
- - The danger zone of the workspace delete page is a critical area that requires careful - consideration and attention. When deleting a workspace, all of the data and resources within - that workspace will be permanently removed and cannot be recovered. - -
- -
+ + +
+ + The danger zone of the workspace delete page is a critical area that requires careful + consideration and attention. When deleting a workspace, all of the data and resources within + that workspace will be permanently removed and cannot be recovered. + +
+
- - -
- )} - - )} -
- ) : ( -
- -
- )} -
- +
+ + +
+ )} + + )} +
+ ) : ( +
+ +
+ )} + ); }; diff --git a/web/pages/[workspaceSlug]/settings/integrations.tsx b/web/pages/[workspaceSlug]/settings/integrations.tsx index 783a1c933..1ec29736e 100644 --- a/web/pages/[workspaceSlug]/settings/integrations.tsx +++ b/web/pages/[workspaceSlug]/settings/integrations.tsx @@ -1,82 +1,54 @@ 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 ( - router.back()}> - - -

{`${truncateText(activeWorkspace?.name ?? "Workspace", 32)}`}

-
- - } - /> - - - } - > -
-
- + }> +
+ +
+ {appIntegrations ? ( + appIntegrations.map((integration) => ( + + )) + ) : ( + + + + + )}
-
- -
- {appIntegrations ? ( - appIntegrations.map((integration) => ( - - )) - ) : ( - - - - - )} -
-
-
- + + ); }; diff --git a/web/pages/[workspaceSlug]/settings/members.tsx b/web/pages/[workspaceSlug]/settings/members.tsx index f1bc89588..5d5582936 100644 --- a/web/pages/[workspaceSlug]/settings/members.tsx +++ b/web/pages/[workspaceSlug]/settings/members.tsx @@ -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 ( - router.back()}> - - -

{`${truncateText(activeWorkspace?.name ?? "Workspace", 32)}`}

-
- - } - /> - - - } - > + }> { @@ -181,155 +161,150 @@ const MembersSettings: NextPage = () => { user={user} onSuccess={handleInviteModalSuccess} /> -
-
- +
+
+

Members

+
-
-
-

Members

- -
- {!workspaceMembers || !workspaceInvitations ? ( - - - - - - - ) : ( -
- {members.length > 0 - ? members.map((member) => ( -
-
- {member.avatar && member.avatar !== "" ? ( + {!workspaceMembers || !workspaceInvitations ? ( + + + + + + + ) : ( +
+ {members.length > 0 + ? members.map((member) => ( +
+
+ {member.avatar && member.avatar !== "" ? ( + + + {member.display_name + + + ) : member.display_name || member.email ? ( + + + {(member.display_name || member.email)?.charAt(0)} + + + ) : ( +
+ ? +
+ )} +
+ {member.member ? ( - - {member.display_name - - - ) : member.display_name || member.email ? ( - - - {(member.display_name || member.email)?.charAt(0)} + + + {member.first_name} {member.last_name} + + ({member.display_name}) ) : ( -
- ? -
+

{member.display_name || member.email}

)} -
- {member.member ? ( - - - - {member.first_name} {member.last_name} - - ({member.display_name}) - - - ) : ( -

{member.display_name || member.email}

- )} - {isOwner &&

{member.email}

} -
-
-
- {!member?.status && ( -
-

Pending

-
- )} - {member?.status && !member?.accountCreated && ( -
-

Account not created

-
- )} - - - {ROLE[member.role as keyof typeof ROLE]} - - {member.memberId !== user?.id && } -
- } - value={member.role} - onChange={(value: 5 | 10 | 15 | 20 | undefined) => { - if (!workspaceSlug) return; - - mutateMembers( - (prevData: any) => - prevData?.map((m: any) => (m.id === member.id ? { ...m, role: value } : m)), - false - ); - - workspaceService - .updateWorkspaceMember(workspaceSlug?.toString(), member.id, { - role: value, - }) - .catch(() => { - setToastAlert({ - type: "error", - title: "Error!", - message: "An error occurred while updating member role. Please try again.", - }); - }); - }} - disabled={ - member.memberId === currentUser?.member.id || - !member.status || - (currentUser && currentUser.role !== 20 && currentUser.role < member.role) - } - > - {Object.keys(ROLE).map((key) => { - if (currentUser && currentUser.role !== 20 && currentUser.role < parseInt(key)) return null; - - return ( - - <>{ROLE[parseInt(key) as keyof typeof ROLE]} - - ); - })} - - - { - if (member.member) { - setSelectedRemoveMember(member.id); - } else { - setSelectedInviteRemoveMember(member.id); - } - }} - > - - - - {user?.id === member.memberId ? "Leave" : "Remove member"} - - - + {isOwner &&

{member.email}

}
- )) - : null} -
- )} -
-
- +
+ {!member?.status && ( +
+

Pending

+
+ )} + {member?.status && !member?.accountCreated && ( +
+

Account not created

+
+ )} + + + {ROLE[member.role as keyof typeof ROLE]} + + {member.memberId !== user?.id && } +
+ } + value={member.role} + onChange={(value: 5 | 10 | 15 | 20 | undefined) => { + if (!workspaceSlug) return; + + mutateMembers( + (prevData: any) => + prevData?.map((m: any) => (m.id === member.id ? { ...m, role: value } : m)), + false + ); + + workspaceService + .updateWorkspaceMember(workspaceSlug?.toString(), member.id, { + role: value, + }) + .catch(() => { + setToastAlert({ + type: "error", + title: "Error!", + message: "An error occurred while updating member role. Please try again.", + }); + }); + }} + disabled={ + member.memberId === currentUser?.member.id || + !member.status || + (currentUser && currentUser.role !== 20 && currentUser.role < member.role) + } + > + {Object.keys(ROLE).map((key) => { + if (currentUser && currentUser.role !== 20 && currentUser.role < parseInt(key)) return null; + + return ( + + <>{ROLE[parseInt(key) as keyof typeof ROLE]} + + ); + })} + + + { + if (member.member) { + setSelectedRemoveMember(member.id); + } else { + setSelectedInviteRemoveMember(member.id); + } + }} + > + + + + {user?.id === member.memberId ? "Leave" : "Remove member"} + + + +
+
+ )) + : null} +
+ )} + + ); };