fix: merge conflicts from preview
This commit is contained in:
commit
3729011cb0
283 changed files with 4895 additions and 5157 deletions
|
|
@ -6,7 +6,10 @@ import { useParams, useSearchParams } from "next/navigation";
|
|||
import { TPageNavigationTabs } from "@plane/types";
|
||||
// components
|
||||
import { PageHead } from "@/components/core";
|
||||
import { EmptyState } from "@/components/empty-state";
|
||||
import { PagesListRoot, PagesListView } from "@/components/pages";
|
||||
// constants
|
||||
import { EmptyStateType } from "@/constants/empty-state";
|
||||
// hooks
|
||||
import { useProject } from "@/hooks/store";
|
||||
|
||||
|
|
@ -16,7 +19,7 @@ const ProjectPagesPage = observer(() => {
|
|||
const type = searchParams.get("type");
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
// store hooks
|
||||
const { getProjectById } = useProject();
|
||||
const { getProjectById, currentProjectDetails } = useProject();
|
||||
// derived values
|
||||
const project = projectId ? getProjectById(projectId.toString()) : undefined;
|
||||
const pageTitle = project?.name ? `${project?.name} - Pages` : undefined;
|
||||
|
|
@ -29,6 +32,17 @@ const ProjectPagesPage = observer(() => {
|
|||
};
|
||||
|
||||
if (!workspaceSlug || !projectId) return <></>;
|
||||
|
||||
// No access to cycle
|
||||
if (currentProjectDetails?.page_view === false)
|
||||
return (
|
||||
<div className="flex items-center justify-center h-full w-full">
|
||||
<EmptyState
|
||||
type={EmptyStateType.DISABLED_PROJECT_PAGE}
|
||||
primaryButtonLink={`/${workspaceSlug}/projects/${projectId}/settings/features`}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<PageHead title={pageTitle} />
|
||||
|
|
|
|||
|
|
@ -7,10 +7,9 @@ import { IProject } from "@plane/types";
|
|||
// ui
|
||||
import { TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// components
|
||||
import { NotAuthorizedView } from "@/components/auth-screens";
|
||||
import { AutoArchiveAutomation, AutoCloseAutomation } from "@/components/automation";
|
||||
import { PageHead } from "@/components/core";
|
||||
// constants
|
||||
import { EUserProjectRoles } from "@/constants/project";
|
||||
// hooks
|
||||
import { useProject, useUser } from "@/hooks/store";
|
||||
|
||||
|
|
@ -19,6 +18,7 @@ const AutomationSettingsPage = observer(() => {
|
|||
const { workspaceSlug, projectId } = useParams();
|
||||
// store hooks
|
||||
const {
|
||||
canPerformProjectAdminActions,
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
const { currentProjectDetails: projectDetails, updateProject } = useProject();
|
||||
|
|
@ -36,13 +36,16 @@ const AutomationSettingsPage = observer(() => {
|
|||
};
|
||||
|
||||
// derived values
|
||||
const isAdmin = currentProjectRole === EUserProjectRoles.ADMIN;
|
||||
const pageTitle = projectDetails?.name ? `${projectDetails?.name} - Automations` : undefined;
|
||||
|
||||
if (currentProjectRole && !canPerformProjectAdminActions) {
|
||||
return <NotAuthorizedView section="settings" isProjectView />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHead title={pageTitle} />
|
||||
<section className={`w-full overflow-y-auto py-8 pr-9 ${isAdmin ? "" : "opacity-60"}`}>
|
||||
<section className={`w-full overflow-y-auto py-8 pr-9 ${canPerformProjectAdminActions ? "" : "opacity-60"}`}>
|
||||
<div className="flex items-center border-b border-custom-border-100 py-3.5">
|
||||
<h3 className="text-xl font-medium">Automations</h3>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,30 +3,40 @@
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// components
|
||||
import { NotAuthorizedView } from "@/components/auth-screens";
|
||||
import { PageHead } from "@/components/core";
|
||||
import { EstimateRoot } from "@/components/estimates";
|
||||
// constants
|
||||
import { EUserProjectRoles } from "@/constants/project";
|
||||
// hooks
|
||||
import { useUser, useProject } from "@/hooks/store";
|
||||
|
||||
const EstimatesSettingsPage = observer(() => {
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
const {
|
||||
canPerformProjectAdminActions,
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
const { currentProjectDetails } = useProject();
|
||||
|
||||
// derived values
|
||||
const isAdmin = currentProjectRole === EUserProjectRoles.ADMIN;
|
||||
const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - Estimates` : undefined;
|
||||
|
||||
if (!workspaceSlug || !projectId) return <></>;
|
||||
|
||||
if (currentProjectRole && !canPerformProjectAdminActions) {
|
||||
return <NotAuthorizedView section="settings" isProjectView />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHead title={pageTitle} />
|
||||
<div className={`w-full overflow-y-auto py-8 pr-9 ${isAdmin ? "" : "pointer-events-none opacity-60"}`}>
|
||||
<EstimateRoot workspaceSlug={workspaceSlug?.toString()} projectId={projectId?.toString()} isAdmin={isAdmin} />
|
||||
<div
|
||||
className={`w-full overflow-y-auto py-8 pr-9 ${canPerformProjectAdminActions ? "" : "pointer-events-none opacity-60"}`}
|
||||
>
|
||||
<EstimateRoot
|
||||
workspaceSlug={workspaceSlug?.toString()}
|
||||
projectId={projectId?.toString()}
|
||||
isAdmin={canPerformProjectAdminActions}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,12 +2,10 @@
|
|||
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
// components
|
||||
import { NotAuthorizedView } from "@/components/auth-screens";
|
||||
import { PageHead } from "@/components/core";
|
||||
import { ProjectFeaturesList } from "@/components/project";
|
||||
// constants
|
||||
import { EUserProjectRoles } from "@/constants/project";
|
||||
// hooks
|
||||
import { useProject, useUser } from "@/hooks/store";
|
||||
|
||||
|
|
@ -15,28 +13,27 @@ const FeaturesSettingsPage = observer(() => {
|
|||
const { workspaceSlug, projectId } = useParams();
|
||||
// store
|
||||
const {
|
||||
membership: { fetchUserProjectInfo },
|
||||
canPerformProjectAdminActions,
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
const { currentProjectDetails } = useProject();
|
||||
// fetch the project details
|
||||
const { data: memberDetails } = useSWR(
|
||||
workspaceSlug && projectId ? `PROJECT_MEMBERS_ME_${workspaceSlug}_${projectId}` : null,
|
||||
workspaceSlug && projectId ? () => fetchUserProjectInfo(workspaceSlug.toString(), projectId.toString()) : null
|
||||
);
|
||||
// derived values
|
||||
const isAdmin = memberDetails?.role === EUserProjectRoles.ADMIN;
|
||||
const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - Features` : undefined;
|
||||
|
||||
if (!workspaceSlug || !projectId) return null;
|
||||
|
||||
if (currentProjectRole && !canPerformProjectAdminActions) {
|
||||
return <NotAuthorizedView section="settings" isProjectView />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHead title={pageTitle} />
|
||||
<section className={`w-full overflow-y-auto py-8 pr-9 ${isAdmin ? "" : "opacity-60"}`}>
|
||||
<section className={`w-full overflow-y-auto py-8 pr-9 ${canPerformProjectAdminActions ? "" : "opacity-60"}`}>
|
||||
<ProjectFeaturesList
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
projectId={projectId.toString()}
|
||||
isAdmin={isAdmin}
|
||||
isAdmin={canPerformProjectAdminActions}
|
||||
/>
|
||||
</section>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ export const ProjectSettingHeader: FC = observer(() => {
|
|||
} = useUser();
|
||||
const { currentProjectDetails, loader } = useProject();
|
||||
|
||||
if (currentProjectRole && currentProjectRole <= EUserProjectRoles.VIEWER) return null;
|
||||
const projectMemberInfo = currentProjectRole || EUserProjectRoles.GUEST;
|
||||
|
||||
return (
|
||||
<div className="relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 bg-custom-sidebar-background-100 p-4">
|
||||
|
|
@ -72,14 +72,17 @@ export const ProjectSettingHeader: FC = observer(() => {
|
|||
placement="bottom-start"
|
||||
closeOnSelect
|
||||
>
|
||||
{PROJECT_SETTINGS_LINKS.map((item) => (
|
||||
<CustomMenu.MenuItem
|
||||
key={item.key}
|
||||
onClick={() => router.push(`/${workspaceSlug}/projects/${projectId}${item.href}`)}
|
||||
>
|
||||
{item.label}
|
||||
</CustomMenu.MenuItem>
|
||||
))}
|
||||
{PROJECT_SETTINGS_LINKS.map(
|
||||
(item) =>
|
||||
projectMemberInfo >= item.access && (
|
||||
<CustomMenu.MenuItem
|
||||
key={item.key}
|
||||
onClick={() => router.push(`/${workspaceSlug}/projects/${projectId}${item.href}`)}
|
||||
>
|
||||
{item.label}
|
||||
</CustomMenu.MenuItem>
|
||||
)
|
||||
)}
|
||||
</CustomMenu>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,13 +5,19 @@ import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine";
|
|||
import { autoScrollForElements } from "@atlaskit/pragmatic-drag-and-drop-auto-scroll/element";
|
||||
import { observer } from "mobx-react";
|
||||
// components
|
||||
import { NotAuthorizedView } from "@/components/auth-screens";
|
||||
import { PageHead } from "@/components/core";
|
||||
import { ProjectSettingsLabelList } from "@/components/labels";
|
||||
// hooks
|
||||
import { useProject } from "@/hooks/store";
|
||||
import { useProject, useUser } from "@/hooks/store";
|
||||
|
||||
const LabelsSettingsPage = observer(() => {
|
||||
// store hooks
|
||||
const { currentProjectDetails } = useProject();
|
||||
const {
|
||||
canPerformProjectMemberActions,
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - Labels` : undefined;
|
||||
|
||||
const scrollableContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
|
|
@ -29,6 +35,10 @@ const LabelsSettingsPage = observer(() => {
|
|||
);
|
||||
}, [scrollableContainerRef?.current]);
|
||||
|
||||
if (currentProjectRole && !canPerformProjectMemberActions) {
|
||||
return <NotAuthorizedView section="settings" isProjectView />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHead title={pageTitle} />
|
||||
|
|
|
|||
|
|
@ -1,18 +1,8 @@
|
|||
"use client";
|
||||
|
||||
import { FC, ReactNode } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
import { useParams } from "next/navigation";
|
||||
// ui
|
||||
import { Button, LayersIcon } from "@plane/ui";
|
||||
// components
|
||||
import { NotAuthorizedView } from "@/components/auth-screens";
|
||||
import { AppHeader, ContentWrapper } from "@/components/core";
|
||||
// constants
|
||||
import { EUserProjectRoles } from "@/constants/project";
|
||||
// hooks
|
||||
import { useUser } from "@/hooks/store";
|
||||
// local components
|
||||
import { ProjectSettingHeader } from "./header";
|
||||
import { ProjectSettingsSidebar } from "./sidebar";
|
||||
|
|
@ -21,33 +11,8 @@ export interface IProjectSettingLayout {
|
|||
children: ReactNode;
|
||||
}
|
||||
|
||||
const ProjectSettingLayout: FC<IProjectSettingLayout> = observer((props) => {
|
||||
const ProjectSettingLayout: FC<IProjectSettingLayout> = (props) => {
|
||||
const { children } = props;
|
||||
// router
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
// store hooks
|
||||
const {
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
|
||||
const restrictViewSettings = currentProjectRole && currentProjectRole <= EUserProjectRoles.VIEWER;
|
||||
|
||||
if (restrictViewSettings) {
|
||||
return (
|
||||
<NotAuthorizedView
|
||||
type="project"
|
||||
actionButton={
|
||||
//TODO: Create a new component called Button Link to handle such scenarios
|
||||
<Link href={`/${workspaceSlug}/projects/${projectId}/issues`}>
|
||||
<Button variant="primary" size="md" prependIcon={<LayersIcon />}>
|
||||
Go to issues
|
||||
</Button>
|
||||
</Link>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<AppHeader header={<ProjectSettingHeader />} />
|
||||
|
|
@ -63,6 +28,6 @@ const ProjectSettingLayout: FC<IProjectSettingLayout> = observer((props) => {
|
|||
</ContentWrapper>
|
||||
</>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export default ProjectSettingLayout;
|
||||
|
|
|
|||
|
|
@ -2,17 +2,26 @@
|
|||
|
||||
import { observer } from "mobx-react";
|
||||
// components
|
||||
import { NotAuthorizedView } from "@/components/auth-screens";
|
||||
import { PageHead } from "@/components/core";
|
||||
import { ProjectMemberList, ProjectSettingsMemberDefaults } from "@/components/project";
|
||||
// hooks
|
||||
import { useProject } from "@/hooks/store";
|
||||
import { useProject, useUser } from "@/hooks/store";
|
||||
|
||||
const MembersSettingsPage = observer(() => {
|
||||
// store
|
||||
const { currentProjectDetails } = useProject();
|
||||
const {
|
||||
canPerformProjectViewerActions,
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
// derived values
|
||||
const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - Members` : undefined;
|
||||
|
||||
if (currentProjectRole && !canPerformProjectViewerActions) {
|
||||
return <NotAuthorizedView section="settings" isProjectView />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHead title={pageTitle} />
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
// ui
|
||||
|
|
@ -14,7 +15,7 @@ import { useUser } from "@/hooks/store";
|
|||
// plane web constants
|
||||
import { PROJECT_SETTINGS_LINKS } from "@/plane-web/constants/project";
|
||||
|
||||
export const ProjectSettingsSidebar = () => {
|
||||
export const ProjectSettingsSidebar = observer(() => {
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
const pathname = usePathname();
|
||||
// mobx store
|
||||
|
|
@ -62,4 +63,4 @@ export const ProjectSettingsSidebar = () => {
|
|||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,18 +3,27 @@
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// components
|
||||
import { NotAuthorizedView } from "@/components/auth-screens";
|
||||
import { PageHead } from "@/components/core";
|
||||
import { ProjectStateRoot } from "@/components/project-states";
|
||||
// hook
|
||||
import { useProject } from "@/hooks/store";
|
||||
import { useProject, useUser } from "@/hooks/store";
|
||||
|
||||
const StatesSettingsPage = observer(() => {
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
// store
|
||||
const { currentProjectDetails } = useProject();
|
||||
const {
|
||||
canPerformProjectMemberActions,
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
// derived values
|
||||
const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - States` : undefined;
|
||||
|
||||
if (currentProjectRole && !canPerformProjectMemberActions) {
|
||||
return <NotAuthorizedView section="settings" isProjectView />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHead title={pageTitle} />
|
||||
|
|
|
|||
|
|
@ -12,21 +12,17 @@ import { BreadcrumbLink, Logo } from "@/components/common";
|
|||
import { ViewListHeader } from "@/components/views";
|
||||
import { ViewAppliedFiltersList } from "@/components/views/applied-filters";
|
||||
// constants
|
||||
import { EUserProjectRoles } from "@/constants/project";
|
||||
import { EViewAccess } from "@/constants/views";
|
||||
// helpers
|
||||
import { calculateTotalFilters } from "@/helpers/filter.helper";
|
||||
// hooks
|
||||
import { useCommandPalette, useProject, useProjectView, useUser } from "@/hooks/store";
|
||||
import { useCommandPalette, useProject, useProjectView } from "@/hooks/store";
|
||||
|
||||
export const ProjectViewsHeader = observer(() => {
|
||||
// router
|
||||
const { workspaceSlug } = useParams();
|
||||
// store hooks
|
||||
const { toggleCreateViewModal } = useCommandPalette();
|
||||
const {
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
const { currentProjectDetails, loader } = useProject();
|
||||
const { filters, updateFilters, clearAllFilters } = useProjectView();
|
||||
|
||||
|
|
@ -49,9 +45,6 @@ export const ProjectViewsHeader = observer(() => {
|
|||
|
||||
const isFiltersApplied = calculateTotalFilters(filters?.filters ?? {}) !== 0;
|
||||
|
||||
const canUserCreateView =
|
||||
currentProjectRole && [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER].includes(currentProjectRole);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 bg-custom-sidebar-background-100 p-4">
|
||||
|
|
@ -83,13 +76,11 @@ export const ProjectViewsHeader = observer(() => {
|
|||
</div>
|
||||
<div className="flex flex-shrink-0 items-center gap-2">
|
||||
<ViewListHeader />
|
||||
{canUserCreateView && (
|
||||
<div>
|
||||
<Button variant="primary" size="sm" onClick={() => toggleCreateViewModal(true)}>
|
||||
Add view
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<Button variant="primary" size="sm" onClick={() => toggleCreateViewModal(true)}>
|
||||
Add view
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{isFiltersApplied && (
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
"use client";
|
||||
|
||||
import { ReactNode } from "react";
|
||||
// components
|
||||
import { AppHeader, ContentWrapper } from "@/components/core";
|
||||
// local components
|
||||
import { ProjectsListHeader } from "@/plane-web/components/projects/header";
|
||||
import { ProjectsListMobileHeader } from "@/plane-web/components/projects/mobile-header";
|
||||
export default function ProjectListLayout({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<>
|
||||
<AppHeader header={<ProjectsListHeader />} mobileHeader={<ProjectsListMobileHeader />} />
|
||||
<ContentWrapper>{children}</ContentWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
import { ProjectPageRoot } from "@/plane-web/components/projects/page";
|
||||
|
||||
const ProjectsPage = () => <ProjectPageRoot />;
|
||||
export default ProjectsPage;
|
||||
|
|
@ -1,191 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { Search, Briefcase, X, ListFilter } from "lucide-react";
|
||||
// types
|
||||
import { TProjectFilters } from "@plane/types";
|
||||
// ui
|
||||
import { Breadcrumbs, Button } from "@plane/ui";
|
||||
// components
|
||||
import { BreadcrumbLink } from "@/components/common";
|
||||
import { FiltersDropdown } from "@/components/issues";
|
||||
import { ProjectFiltersSelection, ProjectOrderByDropdown } from "@/components/project";
|
||||
// constants
|
||||
import { EUserWorkspaceRoles } from "@/constants/workspace";
|
||||
// helpers
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
import { calculateTotalFilters } from "@/helpers/filter.helper";
|
||||
// hooks
|
||||
import { useCommandPalette, useEventTracker, useMember, useProjectFilter, useUser } from "@/hooks/store";
|
||||
import useOutsideClickDetector from "@/hooks/use-outside-click-detector";
|
||||
|
||||
export const ProjectsListHeader = observer(() => {
|
||||
// router
|
||||
const { workspaceSlug } = useParams();
|
||||
// states
|
||||
const [isSearchOpen, setIsSearchOpen] = useState(false);
|
||||
// refs
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
// store hooks
|
||||
const { toggleCreateProjectModal } = useCommandPalette();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const {
|
||||
membership: { currentWorkspaceRole },
|
||||
} = useUser();
|
||||
const {
|
||||
currentWorkspaceDisplayFilters: displayFilters,
|
||||
currentWorkspaceFilters: filters,
|
||||
updateFilters,
|
||||
updateDisplayFilters,
|
||||
searchQuery,
|
||||
updateSearchQuery,
|
||||
} = useProjectFilter();
|
||||
const {
|
||||
workspace: { workspaceMemberIds },
|
||||
} = useMember();
|
||||
// outside click detector hook
|
||||
useOutsideClickDetector(inputRef, () => {
|
||||
if (isSearchOpen && searchQuery.trim() === "") setIsSearchOpen(false);
|
||||
});
|
||||
// auth
|
||||
const isAuthorizedUser = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER;
|
||||
|
||||
const handleFilters = useCallback(
|
||||
(key: keyof TProjectFilters, value: string | string[]) => {
|
||||
if (!workspaceSlug) return;
|
||||
let newValues = filters?.[key] ?? [];
|
||||
if (Array.isArray(value)) {
|
||||
if (key === "created_at" && newValues.find((v) => v.includes("custom"))) newValues = [];
|
||||
value.forEach((val) => {
|
||||
if (!newValues.includes(val)) newValues.push(val);
|
||||
else newValues.splice(newValues.indexOf(val), 1);
|
||||
});
|
||||
} else {
|
||||
if (filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
|
||||
else {
|
||||
if (key === "created_at") newValues = [value];
|
||||
else newValues.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
updateFilters(workspaceSlug.toString(), { [key]: newValues });
|
||||
},
|
||||
[filters, updateFilters, workspaceSlug]
|
||||
);
|
||||
|
||||
const handleInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === "Escape") {
|
||||
if (searchQuery && searchQuery.trim() !== "") updateSearchQuery("");
|
||||
else setIsSearchOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (searchQuery.trim() !== "") setIsSearchOpen(true);
|
||||
}, [searchQuery]);
|
||||
|
||||
const isFiltersApplied = calculateTotalFilters(filters ?? {}) !== 0;
|
||||
|
||||
return (
|
||||
<div className="relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 bg-custom-sidebar-background-100 p-4">
|
||||
<div className="flex flex-grow items-center gap-2 overflow-ellipsis whitespace-nowrap">
|
||||
<div>
|
||||
<Breadcrumbs>
|
||||
<Breadcrumbs.BreadcrumbItem
|
||||
type="text"
|
||||
link={<BreadcrumbLink label="Projects" icon={<Briefcase className="h-4 w-4 text-custom-text-300" />} />}
|
||||
/>
|
||||
</Breadcrumbs>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full flex items-center justify-end gap-3">
|
||||
<div className="flex items-center">
|
||||
{!isSearchOpen && (
|
||||
<button
|
||||
type="button"
|
||||
className="-mr-1 p-2 hover:bg-custom-background-80 rounded text-custom-text-400 grid place-items-center"
|
||||
onClick={() => {
|
||||
setIsSearchOpen(true);
|
||||
inputRef.current?.focus();
|
||||
}}
|
||||
>
|
||||
<Search className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
)}
|
||||
<div
|
||||
className={cn(
|
||||
"ml-auto flex items-center justify-start gap-1 rounded-md border border-transparent bg-custom-background-100 text-custom-text-400 w-0 transition-[width] ease-linear overflow-hidden opacity-0",
|
||||
{
|
||||
"w-30 md:w-64 px-2.5 py-1.5 border-custom-border-200 opacity-100": isSearchOpen,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<Search className="h-3.5 w-3.5" />
|
||||
<input
|
||||
ref={inputRef}
|
||||
className="w-full max-w-[234px] border-none bg-transparent text-sm text-custom-text-100 placeholder:text-custom-text-400 focus:outline-none"
|
||||
placeholder="Search"
|
||||
value={searchQuery}
|
||||
onChange={(e) => updateSearchQuery(e.target.value)}
|
||||
onKeyDown={handleInputKeyDown}
|
||||
/>
|
||||
{isSearchOpen && (
|
||||
<button
|
||||
type="button"
|
||||
className="grid place-items-center"
|
||||
onClick={() => {
|
||||
updateSearchQuery("");
|
||||
setIsSearchOpen(false);
|
||||
}}
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="hidden md:flex gap-3">
|
||||
<ProjectOrderByDropdown
|
||||
value={displayFilters?.order_by}
|
||||
onChange={(val) => {
|
||||
if (!workspaceSlug || val === displayFilters?.order_by) return;
|
||||
updateDisplayFilters(workspaceSlug.toString(), {
|
||||
order_by: val,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<FiltersDropdown
|
||||
icon={<ListFilter className="h-3 w-3" />}
|
||||
title="Filters"
|
||||
placement="bottom-end"
|
||||
isFiltersApplied={isFiltersApplied}
|
||||
>
|
||||
<ProjectFiltersSelection
|
||||
displayFilters={displayFilters ?? {}}
|
||||
filters={filters ?? {}}
|
||||
handleFiltersUpdate={handleFilters}
|
||||
handleDisplayFiltersUpdate={(val) => {
|
||||
if (!workspaceSlug) return;
|
||||
updateDisplayFilters(workspaceSlug.toString(), val);
|
||||
}}
|
||||
memberIds={workspaceMemberIds ?? undefined}
|
||||
/>
|
||||
</FiltersDropdown>
|
||||
</div>
|
||||
{isAuthorizedUser && (
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setTrackElement("Projects page");
|
||||
toggleCreateProjectModal(true);
|
||||
}}
|
||||
className="items-center gap-1"
|
||||
>
|
||||
<span className="hidden sm:inline-block">Add</span> Project
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
|
@ -4,9 +4,8 @@ import { ReactNode } from "react";
|
|||
// components
|
||||
import { AppHeader, ContentWrapper } from "@/components/core";
|
||||
// local components
|
||||
import { ProjectsListHeader } from "./header";
|
||||
import { ProjectsListMobileHeader } from "./mobile-header";
|
||||
|
||||
import { ProjectsListHeader } from "@/plane-web/components/projects/header";
|
||||
import { ProjectsListMobileHeader } from "@/plane-web/components/projects/mobile-header";
|
||||
export default function ProjectListLayout({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -1,91 +0,0 @@
|
|||
import { useCallback } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// icons
|
||||
import { ChevronDown, ListFilter } from "lucide-react";
|
||||
// types
|
||||
import { TProjectFilters } from "@plane/types";
|
||||
// hooks
|
||||
import { FiltersDropdown } from "@/components/issues/issue-layouts";
|
||||
import { ProjectFiltersSelection, ProjectOrderByDropdown } from "@/components/project/dropdowns";
|
||||
// helpers
|
||||
import { calculateTotalFilters } from "@/helpers/filter.helper";
|
||||
// hooks
|
||||
import { useMember, useProjectFilter } from "@/hooks/store";
|
||||
|
||||
export const ProjectsListMobileHeader = observer(() => {
|
||||
// router
|
||||
const { workspaceSlug } = useParams();
|
||||
const {
|
||||
currentWorkspaceDisplayFilters: displayFilters,
|
||||
currentWorkspaceFilters: filters,
|
||||
updateDisplayFilters,
|
||||
updateFilters,
|
||||
} = useProjectFilter();
|
||||
|
||||
|
||||
const {
|
||||
workspace: { workspaceMemberIds },
|
||||
} = useMember();
|
||||
|
||||
const handleFilters = useCallback(
|
||||
(key: keyof TProjectFilters, value: string | string[]) => {
|
||||
if (!workspaceSlug) return;
|
||||
const newValues = filters?.[key] ?? [];
|
||||
if (Array.isArray(value))
|
||||
value.forEach((val) => {
|
||||
if (!newValues.includes(val)) newValues.push(val);
|
||||
else newValues.splice(newValues.indexOf(val), 1);
|
||||
});
|
||||
else {
|
||||
if (filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
|
||||
else newValues.push(value);
|
||||
}
|
||||
updateFilters(workspaceSlug.toString(), { [key]: newValues });
|
||||
},
|
||||
[filters, updateFilters, workspaceSlug]
|
||||
);
|
||||
|
||||
const isFiltersApplied = calculateTotalFilters(filters ?? {}) !== 0;
|
||||
|
||||
return (
|
||||
<div className="flex py-2 border-b border-custom-border-200 md:hidden bg-custom-background-100 w-full">
|
||||
<ProjectOrderByDropdown
|
||||
value={displayFilters?.order_by}
|
||||
onChange={(val) => {
|
||||
if (!workspaceSlug || val === displayFilters?.order_by) return;
|
||||
updateDisplayFilters(workspaceSlug.toString(), {
|
||||
order_by: val,
|
||||
});
|
||||
}}
|
||||
isMobile
|
||||
/>
|
||||
<div className="border-l border-custom-border-200 flex justify-around w-full">
|
||||
<FiltersDropdown
|
||||
icon={<ListFilter className="h-3 w-3" />}
|
||||
title="Filters"
|
||||
placement="bottom-end"
|
||||
menuButton={
|
||||
<div className="flex text-sm items-center gap-2 neutral-primary text-custom-text-200">
|
||||
<ListFilter className="h-3 w-3" />
|
||||
Filters
|
||||
<ChevronDown className="h-3 w-3" strokeWidth={2} />
|
||||
</div>
|
||||
}
|
||||
isFiltersApplied={isFiltersApplied}
|
||||
>
|
||||
<ProjectFiltersSelection
|
||||
displayFilters={displayFilters ?? {}}
|
||||
filters={filters ?? {}}
|
||||
handleFiltersUpdate={handleFilters}
|
||||
handleDisplayFiltersUpdate={(val) => {
|
||||
if (!workspaceSlug) return;
|
||||
updateDisplayFilters(workspaceSlug.toString(), val);
|
||||
}}
|
||||
memberIds={workspaceMemberIds ?? undefined}
|
||||
/>
|
||||
</FiltersDropdown>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
|
@ -1,84 +1,4 @@
|
|||
"use client";
|
||||
|
||||
import { useCallback } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// types
|
||||
import { TProjectAppliedDisplayFilterKeys, TProjectFilters } from "@plane/types";
|
||||
// components
|
||||
import { PageHead } from "@/components/core";
|
||||
import { ProjectAppliedFiltersList, ProjectCardList } from "@/components/project";
|
||||
// helpers
|
||||
import { calculateTotalFilters } from "@/helpers/filter.helper";
|
||||
// hooks
|
||||
import { useProject, useProjectFilter, useWorkspace } from "@/hooks/store";
|
||||
|
||||
const ProjectsPage = observer(() => {
|
||||
// store
|
||||
const { workspaceSlug } = useParams();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { totalProjectIds, filteredProjectIds } = useProject();
|
||||
const {
|
||||
currentWorkspaceFilters,
|
||||
currentWorkspaceAppliedDisplayFilters,
|
||||
clearAllFilters,
|
||||
clearAllAppliedDisplayFilters,
|
||||
updateFilters,
|
||||
updateDisplayFilters,
|
||||
} = useProjectFilter();
|
||||
// derived values
|
||||
const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Projects` : undefined;
|
||||
|
||||
const handleRemoveFilter = useCallback(
|
||||
(key: keyof TProjectFilters, value: string | null) => {
|
||||
if (!workspaceSlug) return;
|
||||
let newValues = currentWorkspaceFilters?.[key] ?? [];
|
||||
|
||||
if (!value) newValues = [];
|
||||
else newValues = newValues.filter((val) => val !== value);
|
||||
|
||||
updateFilters(workspaceSlug.toString(), { [key]: newValues });
|
||||
},
|
||||
[currentWorkspaceFilters, updateFilters, workspaceSlug]
|
||||
);
|
||||
|
||||
const handleRemoveDisplayFilter = useCallback(
|
||||
(key: TProjectAppliedDisplayFilterKeys) => {
|
||||
if (!workspaceSlug) return;
|
||||
updateDisplayFilters(workspaceSlug.toString(), { [key]: false });
|
||||
},
|
||||
[updateDisplayFilters, workspaceSlug]
|
||||
);
|
||||
|
||||
const handleClearAllFilters = useCallback(() => {
|
||||
if (!workspaceSlug) return;
|
||||
clearAllFilters(workspaceSlug.toString());
|
||||
clearAllAppliedDisplayFilters(workspaceSlug.toString());
|
||||
}, [clearAllFilters, clearAllAppliedDisplayFilters, workspaceSlug]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHead title={pageTitle} />
|
||||
<div className="flex h-full w-full flex-col">
|
||||
{(calculateTotalFilters(currentWorkspaceFilters ?? {}) !== 0 ||
|
||||
currentWorkspaceAppliedDisplayFilters?.length !== 0) && (
|
||||
<div className="border-b border-custom-border-200 px-5 py-3">
|
||||
<ProjectAppliedFiltersList
|
||||
appliedFilters={currentWorkspaceFilters ?? {}}
|
||||
appliedDisplayFilters={currentWorkspaceAppliedDisplayFilters ?? []}
|
||||
handleClearAllFilters={handleClearAllFilters}
|
||||
handleRemoveFilter={handleRemoveFilter}
|
||||
handleRemoveDisplayFilter={handleRemoveDisplayFilter}
|
||||
filteredProjects={filteredProjectIds?.length ?? 0}
|
||||
totalProjects={totalProjectIds?.length ?? 0}
|
||||
alwaysAllowEditing
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<ProjectCardList />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
import { ProjectPageRoot } from "@/plane-web/components/projects/page";
|
||||
|
||||
const ProjectsPage = () => <ProjectPageRoot />;
|
||||
export default ProjectsPage;
|
||||
|
|
|
|||
|
|
@ -8,13 +8,13 @@ import useSWR from "swr";
|
|||
import { Button } from "@plane/ui";
|
||||
// component
|
||||
import { ApiTokenListItem, CreateApiTokenModal } from "@/components/api-token";
|
||||
import { NotAuthorizedView } from "@/components/auth-screens";
|
||||
import { PageHead } from "@/components/core";
|
||||
import { EmptyState } from "@/components/empty-state";
|
||||
import { APITokenSettingsLoader } from "@/components/ui";
|
||||
// constants
|
||||
import { EmptyStateType } from "@/constants/empty-state";
|
||||
import { API_TOKENS_LIST } from "@/constants/fetch-keys";
|
||||
import { EUserWorkspaceRoles } from "@/constants/workspace";
|
||||
// store hooks
|
||||
import { useUser, useWorkspace } from "@/hooks/store";
|
||||
// services
|
||||
|
|
@ -29,27 +29,22 @@ const ApiTokensPage = observer(() => {
|
|||
const { workspaceSlug } = useParams();
|
||||
// store hooks
|
||||
const {
|
||||
canPerformWorkspaceAdminActions,
|
||||
membership: { currentWorkspaceRole },
|
||||
} = useUser();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
|
||||
const isAdmin = currentWorkspaceRole === EUserWorkspaceRoles.ADMIN;
|
||||
|
||||
const { data: tokens } = useSWR(workspaceSlug && isAdmin ? API_TOKENS_LIST(workspaceSlug.toString()) : null, () =>
|
||||
workspaceSlug && isAdmin ? apiTokenService.getApiTokens(workspaceSlug.toString()) : null
|
||||
const { data: tokens } = useSWR(
|
||||
workspaceSlug && canPerformWorkspaceAdminActions ? API_TOKENS_LIST(workspaceSlug.toString()) : null,
|
||||
() =>
|
||||
workspaceSlug && canPerformWorkspaceAdminActions ? apiTokenService.getApiTokens(workspaceSlug.toString()) : null
|
||||
);
|
||||
|
||||
const pageTitle = currentWorkspace?.name ? `${currentWorkspace.name} - API Tokens` : undefined;
|
||||
|
||||
if (!isAdmin)
|
||||
return (
|
||||
<>
|
||||
<PageHead title={pageTitle} />
|
||||
<div className="mt-10 flex h-full w-full justify-center p-4">
|
||||
<p className="text-sm text-custom-text-300">You are not authorized to access this page.</p>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
if (currentWorkspaceRole && !canPerformWorkspaceAdminActions) {
|
||||
return <NotAuthorizedView section="settings" />;
|
||||
}
|
||||
|
||||
if (!tokens) {
|
||||
return <APITokenSettingsLoader />;
|
||||
|
|
@ -92,4 +87,4 @@ const ApiTokensPage = observer(() => {
|
|||
);
|
||||
});
|
||||
|
||||
export default ApiTokensPage;
|
||||
export default ApiTokensPage;
|
||||
|
|
|
|||
|
|
@ -2,9 +2,8 @@
|
|||
|
||||
import { observer } from "mobx-react";
|
||||
// component
|
||||
import { NotAuthorizedView } from "@/components/auth-screens";
|
||||
import { PageHead } from "@/components/core";
|
||||
// constants
|
||||
import { EUserWorkspaceRoles } from "@/constants/workspace";
|
||||
// hooks
|
||||
import { useUser, useWorkspace } from "@/hooks/store";
|
||||
// plane web components
|
||||
|
|
@ -13,22 +12,16 @@ import { BillingRoot } from "@/plane-web/components/workspace";
|
|||
const BillingSettingsPage = observer(() => {
|
||||
// store hooks
|
||||
const {
|
||||
canPerformWorkspaceAdminActions,
|
||||
membership: { currentWorkspaceRole },
|
||||
} = useUser();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
// derived values
|
||||
const isAdmin = currentWorkspaceRole === EUserWorkspaceRoles.ADMIN;
|
||||
const pageTitle = currentWorkspace?.name ? `${currentWorkspace.name} - Billing & Plans` : undefined;
|
||||
|
||||
if (!isAdmin)
|
||||
return (
|
||||
<>
|
||||
<PageHead title={pageTitle} />
|
||||
<div className="mt-10 flex h-full w-full justify-center p-4">
|
||||
<p className="text-sm text-custom-text-300">You are not authorized to access this page.</p>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
if (currentWorkspaceRole && !canPerformWorkspaceAdminActions) {
|
||||
return <NotAuthorizedView section="settings" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -2,39 +2,39 @@
|
|||
|
||||
import { observer } from "mobx-react";
|
||||
// components
|
||||
import { NotAuthorizedView } from "@/components/auth-screens";
|
||||
import { PageHead } from "@/components/core";
|
||||
import ExportGuide from "@/components/exporter/guide";
|
||||
// constants
|
||||
import { EUserWorkspaceRoles } from "@/constants/workspace";
|
||||
// helpers
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
// hooks
|
||||
import { useUser, useWorkspace } from "@/hooks/store";
|
||||
|
||||
const ExportsPage = observer(() => {
|
||||
// store hooks
|
||||
const {
|
||||
canPerformWorkspaceViewerActions,
|
||||
canPerformWorkspaceMemberActions,
|
||||
membership: { currentWorkspaceRole },
|
||||
} = useUser();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
|
||||
// derived values
|
||||
const hasPageAccess =
|
||||
currentWorkspaceRole && [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER].includes(currentWorkspaceRole);
|
||||
const pageTitle = currentWorkspace?.name ? `${currentWorkspace.name} - Exports` : undefined;
|
||||
|
||||
if (!hasPageAccess)
|
||||
return (
|
||||
<>
|
||||
<PageHead title={pageTitle} />
|
||||
<div className="mt-10 flex h-full w-full justify-center p-4">
|
||||
<p className="text-sm text-custom-text-300">You are not authorized to access this page.</p>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
// if user is not authorized to view this page
|
||||
if (currentWorkspaceRole && !canPerformWorkspaceViewerActions) {
|
||||
return <NotAuthorizedView section="settings" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHead title={pageTitle} />
|
||||
<div className="w-full overflow-y-auto md:pr-9 pr-4">
|
||||
<div
|
||||
className={cn("w-full overflow-y-auto md:pr-9 pr-4", {
|
||||
"opacity-60": !canPerformWorkspaceMemberActions,
|
||||
})}
|
||||
>
|
||||
<div className="flex items-center border-b border-custom-border-100 py-3.5">
|
||||
<h3 className="text-xl font-medium">Exports</h3>
|
||||
</div>
|
||||
|
|
@ -44,4 +44,4 @@ const ExportsPage = observer(() => {
|
|||
);
|
||||
});
|
||||
|
||||
export default ExportsPage;
|
||||
export default ExportsPage;
|
||||
|
|
|
|||
|
|
@ -9,12 +9,13 @@ import { IWorkspaceBulkInviteFormData } from "@plane/types";
|
|||
// ui
|
||||
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// components
|
||||
import { NotAuthorizedView } from "@/components/auth-screens";
|
||||
import { PageHead } from "@/components/core";
|
||||
import { SendWorkspaceInvitationModal, WorkspaceMembersList } from "@/components/workspace";
|
||||
// constants
|
||||
import { MEMBER_INVITED } from "@/constants/event-tracker";
|
||||
import { EUserWorkspaceRoles } from "@/constants/workspace";
|
||||
// helpers
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
import { getUserRole } from "@/helpers/user.helper";
|
||||
// hooks
|
||||
import { useEventTracker, useMember, useUser, useWorkspace } from "@/hooks/store";
|
||||
|
|
@ -28,6 +29,9 @@ const WorkspaceMembersSettingsPage = observer(() => {
|
|||
// store hooks
|
||||
const { captureEvent } = useEventTracker();
|
||||
const {
|
||||
canPerformWorkspaceAdminActions,
|
||||
canPerformWorkspaceViewerActions,
|
||||
canPerformWorkspaceMemberActions,
|
||||
membership: { currentWorkspaceRole },
|
||||
} = useUser();
|
||||
const {
|
||||
|
|
@ -79,9 +83,13 @@ const WorkspaceMembersSettingsPage = observer(() => {
|
|||
};
|
||||
|
||||
// derived values
|
||||
const isAdmin = currentWorkspaceRole && [EUserWorkspaceRoles.ADMIN].includes(currentWorkspaceRole);
|
||||
const pageTitle = currentWorkspace?.name ? `${currentWorkspace.name} - Members` : undefined;
|
||||
|
||||
// if user is not authorized to view this page
|
||||
if (currentWorkspaceRole && !canPerformWorkspaceViewerActions) {
|
||||
return <NotAuthorizedView section="settings" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHead title={pageTitle} />
|
||||
|
|
@ -90,7 +98,11 @@ const WorkspaceMembersSettingsPage = observer(() => {
|
|||
onClose={() => setInviteModal(false)}
|
||||
onSubmit={handleWorkspaceInvite}
|
||||
/>
|
||||
<section className="w-full overflow-y-auto md:pr-9 pr-4">
|
||||
<section
|
||||
className={cn("w-full overflow-y-auto md:pr-9 pr-4", {
|
||||
"opacity-60": !canPerformWorkspaceMemberActions,
|
||||
})}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-4 py-3.5">
|
||||
<h4 className="text-xl font-medium">Members</h4>
|
||||
<div className="ml-auto flex items-center gap-1.5 rounded-md border border-custom-border-200 bg-custom-background-100 px-2.5 py-1.5">
|
||||
|
|
@ -103,13 +115,13 @@ const WorkspaceMembersSettingsPage = observer(() => {
|
|||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
{isAdmin && (
|
||||
{canPerformWorkspaceAdminActions && (
|
||||
<Button variant="primary" size="sm" onClick={() => setInviteModal(true)}>
|
||||
Add member
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<WorkspaceMembersList searchQuery={searchQuery} isAdmin={isAdmin ?? false} />
|
||||
<WorkspaceMembersList searchQuery={searchQuery} isAdmin={canPerformWorkspaceAdminActions} />
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import useSWR from "swr";
|
|||
// ui
|
||||
import { Button } from "@plane/ui";
|
||||
// components
|
||||
import { NotAuthorizedView } from "@/components/auth-screens";
|
||||
import { PageHead } from "@/components/core";
|
||||
import { EmptyState } from "@/components/empty-state";
|
||||
import { WebhookSettingsLoader } from "@/components/ui";
|
||||
|
|
@ -23,16 +24,15 @@ const WebhooksListPage = observer(() => {
|
|||
const { workspaceSlug } = useParams();
|
||||
// mobx store
|
||||
const {
|
||||
canPerformWorkspaceAdminActions,
|
||||
membership: { currentWorkspaceRole },
|
||||
} = useUser();
|
||||
const { fetchWebhooks, webhooks, clearSecretKey, webhookSecretKey, createWebhook } = useWebhook();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
|
||||
const isAdmin = currentWorkspaceRole === 20;
|
||||
|
||||
useSWR(
|
||||
workspaceSlug && isAdmin ? `WEBHOOKS_LIST_${workspaceSlug}` : null,
|
||||
workspaceSlug && isAdmin ? () => fetchWebhooks(workspaceSlug.toString()) : null
|
||||
workspaceSlug && canPerformWorkspaceAdminActions ? `WEBHOOKS_LIST_${workspaceSlug}` : null,
|
||||
workspaceSlug && canPerformWorkspaceAdminActions ? () => fetchWebhooks(workspaceSlug.toString()) : null
|
||||
);
|
||||
|
||||
const pageTitle = currentWorkspace?.name ? `${currentWorkspace.name} - Webhooks` : undefined;
|
||||
|
|
@ -42,15 +42,9 @@ const WebhooksListPage = observer(() => {
|
|||
if (!showCreateWebhookModal && webhookSecretKey) clearSecretKey();
|
||||
}, [showCreateWebhookModal, webhookSecretKey, clearSecretKey]);
|
||||
|
||||
if (!isAdmin)
|
||||
return (
|
||||
<>
|
||||
<PageHead title={pageTitle} />
|
||||
<div className="mt-10 flex h-full w-full justify-center p-4">
|
||||
<p className="text-sm text-custom-text-300">You are not authorized to access this page.</p>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
if (currentWorkspaceRole && !canPerformWorkspaceAdminActions) {
|
||||
return <NotAuthorizedView section="settings" />;
|
||||
}
|
||||
|
||||
if (!webhooks) return <WebhookSettingsLoader />;
|
||||
|
||||
|
|
@ -95,4 +89,4 @@ const WebhooksListPage = observer(() => {
|
|||
);
|
||||
});
|
||||
|
||||
export default WebhooksListPage;
|
||||
export default WebhooksListPage;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import {
|
|||
import { SidebarFavoritesMenu } from "@/components/workspace/sidebar/favorites/favorites-menu";
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
// hooks
|
||||
import { useAppTheme } from "@/hooks/store";
|
||||
import { useAppTheme, useUser } from "@/hooks/store";
|
||||
import useOutsideClickDetector from "@/hooks/use-outside-click-detector";
|
||||
// plane web components
|
||||
import useSize from "@/hooks/use-window-size";
|
||||
|
|
@ -23,6 +23,7 @@ export interface IAppSidebar {}
|
|||
|
||||
export const AppSidebar: FC<IAppSidebar> = observer(() => {
|
||||
// store hooks
|
||||
const { canPerformWorkspaceMemberActions } = useUser();
|
||||
const { toggleSidebar, sidebarCollapsed } = useAppTheme();
|
||||
const windowSize = useSize();
|
||||
// refs
|
||||
|
|
@ -54,10 +55,14 @@ export const AppSidebar: FC<IAppSidebar> = observer(() => {
|
|||
<div
|
||||
ref={ref}
|
||||
className={cn("size-full flex flex-col flex-1 pt-4 pb-0", {
|
||||
"p-2": sidebarCollapsed,
|
||||
"p-2 pt-4": sidebarCollapsed,
|
||||
})}
|
||||
>
|
||||
<div className="px-4">
|
||||
<div
|
||||
className={cn("px-2", {
|
||||
"px-4": !sidebarCollapsed,
|
||||
})}
|
||||
>
|
||||
<SidebarDropdown />
|
||||
<div className="flex-shrink-0 h-4" />
|
||||
<SidebarAppSwitcher />
|
||||
|
|
@ -69,8 +74,8 @@ export const AppSidebar: FC<IAppSidebar> = observer(() => {
|
|||
})}
|
||||
/>
|
||||
<div
|
||||
className={cn("overflow-x-hidden scrollbar-sm h-full w-full overflow-y-auto px-4", {
|
||||
"vertical-scrollbar": !sidebarCollapsed,
|
||||
className={cn("overflow-x-hidden scrollbar-sm h-full w-full overflow-y-auto px-2", {
|
||||
"vertical-scrollbar px-4": !sidebarCollapsed,
|
||||
})}
|
||||
>
|
||||
<SidebarUserMenu />
|
||||
|
|
@ -81,7 +86,7 @@ export const AppSidebar: FC<IAppSidebar> = observer(() => {
|
|||
"opacity-0": !sidebarCollapsed,
|
||||
})}
|
||||
/>
|
||||
<SidebarFavoritesMenu />
|
||||
{canPerformWorkspaceMemberActions && <SidebarFavoritesMenu />}
|
||||
|
||||
<SidebarProjectsList />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -14,11 +14,10 @@ import { DisplayFiltersSelection, FiltersDropdown, FilterSelection } from "@/com
|
|||
import { CreateUpdateWorkspaceViewModal } from "@/components/workspace";
|
||||
// constants
|
||||
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
|
||||
import { EUserWorkspaceRoles } from "@/constants/workspace";
|
||||
// helpers
|
||||
import { isIssueFilterActive } from "@/helpers/filter.helper";
|
||||
// hooks
|
||||
import { useLabel, useMember, useUser, useIssues, useGlobalView } from "@/hooks/store";
|
||||
import { useLabel, useMember, useIssues, useGlobalView } from "@/hooks/store";
|
||||
|
||||
export const GlobalIssuesHeader = observer(() => {
|
||||
// states
|
||||
|
|
@ -30,9 +29,6 @@ export const GlobalIssuesHeader = observer(() => {
|
|||
issuesFilter: { filters, updateFilters },
|
||||
} = useIssues(EIssuesStoreType.GLOBAL);
|
||||
const { getViewDetailsById } = useGlobalView();
|
||||
const {
|
||||
membership: { currentWorkspaceRole },
|
||||
} = useUser();
|
||||
const { workspaceLabels } = useLabel();
|
||||
const {
|
||||
workspace: { workspaceMemberIds },
|
||||
|
|
@ -97,8 +93,6 @@ export const GlobalIssuesHeader = observer(() => {
|
|||
[workspaceSlug, updateFilters, globalViewId]
|
||||
);
|
||||
|
||||
const isAuthorizedUser = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER;
|
||||
|
||||
const isLocked = viewDetails?.is_locked;
|
||||
|
||||
return (
|
||||
|
|
@ -142,11 +136,10 @@ export const GlobalIssuesHeader = observer(() => {
|
|||
</FiltersDropdown>
|
||||
</>
|
||||
)}
|
||||
{isAuthorizedUser && (
|
||||
<Button variant="primary" size="sm" onClick={() => setCreateViewModal(true)}>
|
||||
Add view
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button variant="primary" size="sm" onClick={() => setCreateViewModal(true)}>
|
||||
Add view
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -154,6 +154,7 @@ export default function ForgotPasswordPage() {
|
|||
hasError={Boolean(errors.email)}
|
||||
placeholder="name@company.com"
|
||||
className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
|
||||
autoComplete="on"
|
||||
disabled={resendTimerCode > 0}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -153,6 +153,7 @@ export default function ResetPasswordPage() {
|
|||
//hasError={Boolean(errors.email)}
|
||||
placeholder="name@company.com"
|
||||
className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 text-onboarding-text-400 cursor-not-allowed"
|
||||
autoComplete="on"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -173,6 +174,7 @@ export default function ResetPasswordPage() {
|
|||
minLength={8}
|
||||
onFocus={() => setIsPasswordInputFocused(true)}
|
||||
onBlur={() => setIsPasswordInputFocused(false)}
|
||||
autoComplete="on"
|
||||
autoFocus
|
||||
/>
|
||||
{showPassword.password ? (
|
||||
|
|
|
|||
|
|
@ -147,6 +147,7 @@ const SetPasswordPage = observer(() => {
|
|||
//hasError={Boolean(errors.email)}
|
||||
placeholder="name@company.com"
|
||||
className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 text-onboarding-text-400 cursor-not-allowed"
|
||||
autoComplete="on"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -167,6 +168,7 @@ const SetPasswordPage = observer(() => {
|
|||
minLength={8}
|
||||
onFocus={() => setIsPasswordInputFocused(true)}
|
||||
onBlur={() => setIsPasswordInputFocused(false)}
|
||||
autoComplete="on"
|
||||
autoFocus
|
||||
/>
|
||||
{showPassword.password ? (
|
||||
|
|
|
|||
|
|
@ -245,6 +245,7 @@ const ProfileSettingsPage = observer(() => {
|
|||
placeholder="Enter your first name"
|
||||
className={`w-full rounded-md ${errors.first_name ? "border-red-500" : ""}`}
|
||||
maxLength={24}
|
||||
autoComplete="on"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
|
@ -269,6 +270,7 @@ const ProfileSettingsPage = observer(() => {
|
|||
placeholder="Enter your last name"
|
||||
className="w-full rounded-md"
|
||||
maxLength={24}
|
||||
autoComplete="on"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
|
@ -296,6 +298,7 @@ const ProfileSettingsPage = observer(() => {
|
|||
className={`w-full cursor-not-allowed rounded-md !bg-custom-background-80 ${
|
||||
errors.email ? "border-red-500" : ""
|
||||
}`}
|
||||
autoComplete="on"
|
||||
disabled
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue