Compare commits

...
Author SHA1 Message Date
Aaron Reisman 95cbbbc0f9 [WEB-5167] feat: migrate web to react-router 2025-10-20 18:52:02 -07:00
624 changed files with 6417 additions and 1399 deletions
@@ -14,7 +14,11 @@ import { useAppRail } from "@/hooks/use-app-rail";
import { ExtendedAppSidebar } from "./extended-sidebar";
import { AppSidebar } from "./sidebar";
export const ProjectAppSidebar: FC = observer(() => {
type Props = {
workspaceSlug: string;
};
export const ProjectAppSidebar: FC<Props> = observer(({ workspaceSlug }) => {
// store hooks
const {
sidebarCollapsed,
@@ -51,7 +55,7 @@ export const ProjectAppSidebar: FC = observer(() => {
togglePeek={toggleSidebarPeek}
extendedSidebar={
<>
<ExtendedAppSidebar />
<ExtendedAppSidebar workspaceSlug={workspaceSlug} />
</>
}
isAnyExtendedSidebarExpanded={isAnyExtendedSidebarOpen}
@@ -1,16 +1,17 @@
"use client";
// components
import { Outlet } from "react-router";
import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper";
// local imports
import { WorkspaceActiveCycleHeader } from "./header";
export default function WorkspaceActiveCycleLayout({ children }: { children: React.ReactNode }) {
export default function WorkspaceActiveCycleLayout() {
return (
<>
<AppHeader header={<WorkspaceActiveCycleHeader />} />
<ContentWrapper>{children}</ContentWrapper>
<ContentWrapper>
<Outlet />
</ContentWrapper>
</>
);
}
@@ -8,7 +8,7 @@ import { useWorkspace } from "@/hooks/store/use-workspace";
// plane web components
import { WorkspaceActiveCyclesRoot } from "@/plane-web/components/active-cycles";
const WorkspaceActiveCyclesPage = observer(() => {
function WorkspaceActiveCyclesPage() {
const { currentWorkspace } = useWorkspace();
// derived values
const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Active Cycles` : undefined;
@@ -19,6 +19,6 @@ const WorkspaceActiveCyclesPage = observer(() => {
<WorkspaceActiveCyclesRoot />
</>
);
});
}
export default WorkspaceActiveCyclesPage;
export default observer(WorkspaceActiveCyclesPage);
@@ -1,14 +1,18 @@
"use client";
// components
import { Outlet } from "react-router";
import type { Route } from "./+types/layout";
import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper";
import { WorkspaceAnalyticsHeader } from "./header";
export default function WorkspaceAnalyticsTabLayout({ children }: { children: React.ReactNode }) {
export default function WorkspaceAnalyticsTabLayout() {
return (
<>
<AppHeader header={<WorkspaceAnalyticsHeader />} />
<ContentWrapper>{children}</ContentWrapper>
<ContentWrapper>
<Outlet />
</ContentWrapper>
</>
);
}
@@ -8,6 +8,7 @@ import { EUserPermissions, EUserPermissionsLevel, PROJECT_TRACKER_ELEMENTS } fro
import { useTranslation } from "@plane/i18n";
import { Tabs } from "@plane/ui";
import type { TabItem } from "@plane/ui";
import type { Route } from "./+types/page";
// components
import AnalyticsFilterActions from "@/components/analytics/analytics-filter-actions";
import { PageHead } from "@/components/core/page-title";
@@ -21,18 +22,11 @@ import { useWorkspace } from "@/hooks/store/use-workspace";
import { useUserPermissions } from "@/hooks/store/user";
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
import { getAnalyticsTabs } from "@/plane-web/components/analytics/tabs";
import { AnalyticsWorkspaceProvider } from "@/components/analytics/analytics-context";
type Props = {
params: {
tabId: string;
workspaceSlug: string;
};
};
const AnalyticsPage = observer((props: Props) => {
function AnalyticsPage({ params }: Route.ComponentProps) {
// props
const { params } = props;
const { tabId } = params;
const { tabId, workspaceSlug } = params;
// hooks
const router = useRouter();
@@ -82,18 +76,20 @@ const AnalyticsPage = observer((props: Props) => {
<>
{workspaceProjectIds.length > 0 || loader === "init-loader" ? (
<div className="flex h-full overflow-hidden bg-custom-background-100 justify-between items-center ">
<Tabs
tabs={tabs}
storageKey={`analytics-page-${currentWorkspace?.id}`}
defaultTab={defaultTab}
size="md"
tabListContainerClassName="px-6 py-2 border-b border-custom-border-200 flex items-center justify-between"
tabListClassName="my-2 w-auto"
tabClassName="px-3"
tabPanelClassName="h-full overflow-hidden overflow-y-auto px-2"
storeInLocalStorage={false}
actions={<AnalyticsFilterActions />}
/>
<AnalyticsWorkspaceProvider workspaceSlug={workspaceSlug}>
<Tabs
tabs={tabs}
storageKey={`analytics-page-${currentWorkspace?.id}`}
defaultTab={defaultTab}
size="md"
tabListContainerClassName="px-6 py-2 border-b border-custom-border-200 flex items-center justify-between"
tabListClassName="my-2 w-auto"
tabClassName="px-3"
tabPanelClassName="h-full overflow-hidden overflow-y-auto px-2"
storeInLocalStorage={false}
actions={<AnalyticsFilterActions />}
/>
</AnalyticsWorkspaceProvider>
</div>
) : (
<DetailedEmptyState
@@ -118,6 +114,6 @@ const AnalyticsPage = observer((props: Props) => {
)}
</>
);
});
}
export default AnalyticsPage;
export default observer(AnalyticsPage);
@@ -1,7 +1,6 @@
"use client";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// plane imports
import { EProjectFeatureKey } from "@plane/constants";
import { Breadcrumbs, Header } from "@plane/ui";
@@ -14,30 +13,34 @@ import { useProject } from "@/hooks/store/use-project";
import { useAppRouter } from "@/hooks/use-app-router";
import { CommonProjectBreadcrumbs } from "@/plane-web/components/breadcrumbs/common";
export const ProjectIssueDetailsHeader = observer(() => {
type ProjectIssueDetailsHeaderProps = {
workspaceSlug: string;
workItem: string;
};
export const ProjectIssueDetailsHeader = observer(({ workspaceSlug, workItem }: ProjectIssueDetailsHeaderProps) => {
// router
const router = useAppRouter();
const { workspaceSlug, workItem } = useParams();
// store hooks
const { getProjectById, loader } = useProject();
const {
issue: { getIssueById, getIssueIdByIdentifier },
} = useIssueDetail();
// derived values
const issueId = getIssueIdByIdentifier(workItem?.toString());
const issueDetails = issueId ? getIssueById(issueId.toString()) : undefined;
const issueId = getIssueIdByIdentifier(workItem);
const issueDetails = issueId ? getIssueById(issueId) : undefined;
const projectId = issueDetails ? issueDetails?.project_id : undefined;
const projectDetails = projectId ? getProjectById(projectId?.toString()) : undefined;
const projectDetails = projectId ? getProjectById(projectId) : undefined;
if (!workspaceSlug || !projectId || !issueId) return null;
if (!projectId || !issueId) return null;
return (
<Header>
<Header.LeftItem>
<Breadcrumbs onBack={router.back} isLoading={loader === "init-loader"}>
<CommonProjectBreadcrumbs
workspaceSlug={workspaceSlug?.toString()}
projectId={projectId?.toString()}
workspaceSlug={workspaceSlug}
projectId={projectId}
featureKey={EProjectFeatureKey.WORK_ITEMS}
/>
<Breadcrumbs.Item
@@ -1,15 +1,18 @@
"use client";
// components
import { Outlet } from "react-router";
import type { Route } from "./+types/layout";
import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper";
import { ProjectIssueDetailsHeader } from "./header";
export default function ProjectIssueDetailsLayout({ children }: { children: React.ReactNode }) {
export default function ProjectIssueDetailsLayout({ params }: Route.ComponentProps) {
return (
<>
<AppHeader header={<ProjectIssueDetailsHeader />} />
<ContentWrapper className="overflow-hidden">{children}</ContentWrapper>
<AppHeader header={<ProjectIssueDetailsHeader workspaceSlug={params.workspaceSlug} workItem={params.workItem} />} />
<ContentWrapper className="overflow-hidden">
<Outlet />
</ContentWrapper>
</>
);
}
@@ -2,7 +2,7 @@
import React, { useEffect } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import type { Route } from "./+types/page";
import { useTheme } from "next-themes";
import useSWR from "swr";
// plane imports
@@ -21,13 +21,13 @@ import { useProject } from "@/hooks/store/use-project";
import { useAppRouter } from "@/hooks/use-app-router";
import { useWorkItemProperties } from "@/plane-web/hooks/use-issue-properties";
import { ProjectAuthWrapper } from "@/plane-web/layouts/project-wrapper";
import emptyIssueDark from "@/public/empty-state/search/issues-dark.webp";
import emptyIssueLight from "@/public/empty-state/search/issues-light.webp";
import emptyIssueDark from "@/app/assets/empty-state/search/issues-dark.webp";
import emptyIssueLight from "@/app/assets/empty-state/search/issues-light.webp";
const IssueDetailsPage = observer(() => {
function IssueDetailsPage({ params }: Route.ComponentProps) {
const { workspaceSlug, workItem } = params;
// router
const router = useAppRouter();
const { workspaceSlug, workItem } = useParams();
// hooks
const { resolvedTheme } = useTheme();
// store hooks
@@ -39,15 +39,12 @@ const IssueDetailsPage = observer(() => {
const { getProjectById } = useProject();
const { toggleIssueDetailSidebar, issueDetailSidebarCollapsed } = useAppTheme();
const projectIdentifier = workItem?.toString().split("-")[0];
const sequence_id = workItem?.toString().split("-")[1];
const [projectIdentifier, sequence_id] = workItem.split("-");
// fetching issue details
const { data, isLoading, error } = useSWR(
workspaceSlug && workItem ? `ISSUE_DETAIL_${workspaceSlug}_${projectIdentifier}_${sequence_id}` : null,
workspaceSlug && workItem
? () => fetchIssueWithIdentifier(workspaceSlug.toString(), projectIdentifier, sequence_id)
: null
`ISSUE_DETAIL_${workspaceSlug}_${projectIdentifier}_${sequence_id}`,
() => fetchIssueWithIdentifier(workspaceSlug.toString(), projectIdentifier, sequence_id)
);
const issueId = data?.id;
const projectId = data?.project_id;
@@ -59,7 +56,7 @@ const IssueDetailsPage = observer(() => {
useWorkItemProperties(
projectId,
workspaceSlug.toString(),
workspaceSlug,
issueId,
issue?.is_epic ? EIssueServiceType.EPICS : EIssueServiceType.ISSUES
);
@@ -116,11 +113,11 @@ const IssueDetailsPage = observer(() => {
workspaceSlug &&
projectId &&
issueId && (
<ProjectAuthWrapper workspaceSlug={workspaceSlug?.toString()} projectId={projectId?.toString()}>
<ProjectAuthWrapper workspaceSlug={workspaceSlug} projectId={projectId}>
<IssueDetailRoot
workspaceSlug={workspaceSlug.toString()}
projectId={projectId.toString()}
issueId={issueId.toString()}
workspaceSlug={workspaceSlug}
projectId={projectId}
issueId={issueId}
is_archived={!!issue?.archived_at}
/>
</ProjectAuthWrapper>
@@ -128,6 +125,6 @@ const IssueDetailsPage = observer(() => {
)}
</>
);
});
}
export default IssueDetailsPage;
export default observer(IssueDetailsPage);
@@ -1,16 +1,17 @@
"use client";
// components
import { Outlet } from "react-router";
import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper";
// local imports
import { WorkspaceDraftHeader } from "./header";
export default function WorkspaceDraftLayout({ children }: { children: React.ReactNode }) {
export default function WorkspaceDraftLayout() {
return (
<>
<AppHeader header={<WorkspaceDraftHeader />} />
<ContentWrapper>{children}</ContentWrapper>
<ContentWrapper>
<Outlet />
</ContentWrapper>
</>
);
}
@@ -1,27 +1,18 @@
"use client";
import { useParams } from "next/navigation";
import type { Route } from "./+types/page";
// components
import { PageHead } from "@/components/core/page-title";
import { WorkspaceDraftIssuesRoot } from "@/components/issues/workspace-draft";
const WorkspaceDraftPage = () => {
// router
const { workspaceSlug: routeWorkspaceSlug } = useParams();
const pageTitle = "Workspace Draft";
export default function WorkspaceDraftPage({ params }: Route.ComponentProps) {
const { workspaceSlug } = params;
// derived values
const workspaceSlug = (routeWorkspaceSlug as string) || undefined;
if (!workspaceSlug) return null;
return (
<>
<PageHead title={pageTitle} />
<PageHead title="Workspace Draft" />
<div className="relative h-full w-full overflow-hidden overflow-y-auto">
<WorkspaceDraftIssuesRoot workspaceSlug={workspaceSlug} />
</div>
</>
);
};
export default WorkspaceDraftPage;
}
@@ -1,8 +1,7 @@
"use client";
import React, { useMemo, useRef } from "react";
import { useMemo, useRef } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// plane imports
import { WORKSPACE_SIDEBAR_DYNAMIC_NAVIGATION_ITEMS_LINKS } from "@plane/constants";
import type { EUserWorkspaceRoles } from "@plane/types";
@@ -13,17 +12,19 @@ import { useWorkspace } from "@/hooks/store/use-workspace";
import { ExtendedSidebarItem } from "@/plane-web/components/workspace/sidebar/extended-sidebar-item";
import { ExtendedSidebarWrapper } from "./extended-sidebar-wrapper";
export const ExtendedAppSidebar = observer(() => {
type ExtendedAppSidebarProps = {
workspaceSlug: string;
};
export const ExtendedAppSidebar = observer(({ workspaceSlug }: ExtendedAppSidebarProps) => {
// refs
const extendedSidebarRef = useRef<HTMLDivElement | null>(null);
// routers
const { workspaceSlug } = useParams();
// store hooks
const { isExtendedSidebarOpened, toggleExtendedSidebar } = useAppTheme();
const { updateSidebarPreference, getNavigationPreferences } = useWorkspace();
// derived values
const currentWorkspaceNavigationPreferences = getNavigationPreferences(workspaceSlug.toString());
const currentWorkspaceNavigationPreferences = getNavigationPreferences(workspaceSlug);
const sortedNavigationItems = useMemo(
() =>
@@ -107,6 +108,7 @@ export const ExtendedAppSidebar = observer(() => {
{sortedNavigationItems.map((item, index) => (
<ExtendedSidebarItem
key={item.key}
workspaceSlug={workspaceSlug}
item={item}
isLastChild={index === sortedNavigationItems.length - 1}
handleOnNavigationItemDrop={handleOnNavigationItemDrop}
@@ -1,22 +1,25 @@
"use client";
import { Outlet } from "react-router";
import type { Route } from "../+types/layout";
import { CommandPalette } from "@/components/command-palette";
import { AuthenticationWrapper } from "@/lib/wrappers/authentication-wrapper";
// plane web components
import { WorkspaceAuthWrapper } from "@/plane-web/layouts/workspace-wrapper";
import { ProjectAppSidebar } from "./_sidebar";
export default function WorkspaceLayout({ children }: { children: React.ReactNode }) {
export default function WorkspaceLayout({ params }: Route.ComponentProps) {
const { workspaceSlug } = params;
return (
<AuthenticationWrapper>
<CommandPalette />
<CommandPalette workspaceSlug={workspaceSlug} />
<WorkspaceAuthWrapper>
<div className="relative flex flex-col h-full w-full overflow-hidden rounded-lg border border-custom-border-200">
<div id="full-screen-portal" className="inset-0 absolute w-full" />
<div className="relative flex size-full overflow-hidden">
<ProjectAppSidebar />
<ProjectAppSidebar workspaceSlug={workspaceSlug} />
<main className="relative flex h-full w-full flex-col overflow-hidden bg-custom-background-100">
{children}
<Outlet />
</main>
</div>
</div>
@@ -1,13 +1,15 @@
"use client";
// components
import { Outlet } from "react-router";
import { NotificationsSidebarRoot } from "@/components/workspace-notifications/sidebar";
export default function ProjectInboxIssuesLayout({ children }: { children: React.ReactNode }) {
export default function ProjectInboxIssuesLayout() {
return (
<div className="relative w-full h-full overflow-hidden flex items-center">
<NotificationsSidebarRoot />
<div className="w-full h-full overflow-hidden overflow-y-auto">{children}</div>
<div className="w-full h-full overflow-hidden overflow-y-auto">
<Outlet />
</div>
</div>
);
}
@@ -1,7 +1,7 @@
"use client";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import type { Route } from "./+types/page";
// plane imports
import { useTranslation } from "@plane/i18n";
// components
@@ -10,8 +10,8 @@ import { NotificationsRoot } from "@/components/workspace-notifications";
// hooks
import { useWorkspace } from "@/hooks/store/use-workspace";
const WorkspaceDashboardPage = observer(() => {
const { workspaceSlug } = useParams();
function WorkspaceDashboardPage({ params }: Route.ComponentProps) {
const { workspaceSlug } = params;
// plane hooks
const { t } = useTranslation();
// hooks
@@ -24,9 +24,9 @@ const WorkspaceDashboardPage = observer(() => {
return (
<>
<PageHead title={pageTitle} />
<NotificationsRoot workspaceSlug={workspaceSlug?.toString()} />
<NotificationsRoot workspaceSlug={workspaceSlug} />
</>
);
});
};
export default WorkspaceDashboardPage;
export default observer(WorkspaceDashboardPage);
@@ -1,7 +1,7 @@
"use client";
import React from "react";
import { useParams } from "next/navigation";
import type { Route } from "./+types/page";
// components
import { PageHead } from "@/components/core/page-title";
import { ProfileIssuesPage } from "@/components/profile/profile-issues";
@@ -12,10 +12,16 @@ const ProfilePageHeader = {
subscribed: "Profile - Subscribed",
};
const ProfileIssuesTypePage = () => {
const { profileViewId } = useParams() as { profileViewId: "assigned" | "subscribed" | "created" | undefined };
function isValidProfileViewId(viewId: string): viewId is keyof typeof ProfilePageHeader {
return viewId in ProfilePageHeader;
}
if (!profileViewId) return null;
export default function ProfileIssuesTypePage({ params }: Route.ComponentProps) {
const { profileViewId } = params;
if (!isValidProfileViewId(profileViewId)) {
return null;
}
const header = ProfilePageHeader[profileViewId];
@@ -26,5 +32,3 @@ const ProfileIssuesTypePage = () => {
</>
);
};
export default ProfileIssuesTypePage;
@@ -15,7 +15,7 @@ import { useUserPermissions } from "@/hooks/store/user";
const PER_PAGE = 100;
const ProfileActivityPage = observer(() => {
function ProfileActivityPage() {
// states
const [pageCount, setPageCount] = useState(1);
const [totalPages, setTotalPages] = useState(0);
@@ -69,6 +69,6 @@ const ProfileActivityPage = observer(() => {
</div>
</>
);
});
}
export default ProfileActivityPage;
export default observer(ProfileActivityPage);
@@ -1,7 +1,9 @@
"use client";
import { observer } from "mobx-react";
import { useParams, usePathname } from "next/navigation";
import { Outlet } from "react-router";
import type { Route } from "./+types/layout";
import { usePathname } from "next/navigation";
import useSWR from "swr";
// components
import { PROFILE_VIEWER_TAB, PROFILE_ADMINS_TAB, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
@@ -22,19 +24,11 @@ import { ProfileNavbar } from "./navbar";
const userService = new UserService();
type Props = {
children: React.ReactNode;
};
const UseProfileLayout: React.FC<Props> = observer((props) => {
const { children } = props;
// router
const { workspaceSlug, userId } = useParams();
function UseProfileLayout({ params }: Route.ComponentProps) {
const { workspaceSlug, userId } = params;
const pathname = usePathname();
// store hooks
const { allowPermissions } = useUserPermissions();
const { t } = useTranslation();
// derived values
const isAuthorized = allowPermissions(
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
EUserPermissionsLevel.WORKSPACE
@@ -43,13 +37,9 @@ const UseProfileLayout: React.FC<Props> = observer((props) => {
const windowSize = useSize();
const isSmallerScreen = windowSize[0] >= 768;
const { data: userProjectsData } = useSWR(
workspaceSlug && userId ? USER_PROFILE_PROJECT_SEGREGATION(workspaceSlug.toString(), userId.toString()) : null,
workspaceSlug && userId
? () => userService.getUserProfileProjectsSegregation(workspaceSlug.toString(), userId.toString())
: null
const { data: userProjectsData } = useSWR(USER_PROFILE_PROJECT_SEGREGATION(workspaceSlug, userId), () =>
userService.getUserProfileProjectsSegregation(workspaceSlug, userId)
);
// derived values
const isAuthorizedPath =
pathname.includes("assigned") || pathname.includes("created") || pathname.includes("subscribed");
const isIssuesTab = pathname.includes("assigned") || pathname.includes("created") || pathname.includes("subscribed");
@@ -78,7 +68,9 @@ const UseProfileLayout: React.FC<Props> = observer((props) => {
<div className="flex w-full flex-col md:h-full md:overflow-hidden">
<ProfileNavbar isAuthorized={!!isAuthorized} />
{isAuthorized || !isAuthorizedPath ? (
<div className={`w-full overflow-hidden h-full`}>{children}</div>
<div className={`w-full overflow-hidden h-full`}>
<Outlet />
</div>
) : (
<div className="grid h-full w-full place-items-center text-custom-text-200">
{t("you_do_not_have_the_permission_to_access_this_page")}
@@ -93,6 +85,7 @@ const UseProfileLayout: React.FC<Props> = observer((props) => {
</div>
</>
);
});
}
export default UseProfileLayout;
export default observer(UseProfileLayout);
@@ -1,6 +1,6 @@
"use client";
import { useParams } from "next/navigation";
import type { Route } from "./+types/page";
import useSWR from "swr";
// plane imports
import { GROUP_CHOICES } from "@plane/constants";
@@ -20,13 +20,13 @@ import { USER_PROFILE_DATA } from "@/constants/fetch-keys";
import { UserService } from "@/services/user.service";
const userService = new UserService();
export default function ProfileOverviewPage() {
const { workspaceSlug, userId } = useParams();
export default function ProfileOverviewPage({ params }: Route.ComponentProps) {
const { workspaceSlug, userId } = params;
const { t } = useTranslation();
const { data: userProfile } = useSWR(
workspaceSlug && userId ? USER_PROFILE_DATA(workspaceSlug.toString(), userId.toString()) : null,
workspaceSlug && userId ? () => userService.getUserProfileData(workspaceSlug.toString(), userId.toString()) : null
USER_PROFILE_DATA(workspaceSlug, userId),
() => userService.getUserProfileData(workspaceSlug, userId)
);
const stateDistribution: IUserStateDistribution[] = Object.keys(GROUP_CHOICES).map((key) => {
@@ -1,15 +1,21 @@
"use client";
import { Outlet } from "react-router";
import type { Route } from "./+types/layout";
// components
import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper";
import { ProjectArchivesHeader } from "../header";
export default function ProjectArchiveCyclesLayout({ children }: { children: React.ReactNode }) {
export default function ProjectArchiveCyclesLayout({ params }: Route.ComponentProps) {
const { workspaceSlug, projectId } = params;
return (
<>
<AppHeader header={<ProjectArchivesHeader activeTab="cycles" />} />
<ContentWrapper>{children}</ContentWrapper>
<AppHeader
header={<ProjectArchivesHeader activeTab="cycles" workspaceSlug={workspaceSlug} projectId={projectId} />}
/>
<ContentWrapper>
<Outlet />
</ContentWrapper>
</>
);
}
@@ -1,7 +1,7 @@
"use client";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import type { Route } from "./+types/page";
// components
import { PageHead } from "@/components/core/page-title";
import { ArchivedCycleLayoutRoot } from "@/components/cycles/archived-cycles";
@@ -9,9 +9,9 @@ import { ArchivedCyclesHeader } from "@/components/cycles/archived-cycles/header
// hooks
import { useProject } from "@/hooks/store/use-project";
const ProjectArchivedCyclesPage = observer(() => {
function ProjectArchivedCyclesPage({ params }: Route.ComponentProps) {
// router
const { projectId } = useParams();
const { projectId } = params;
// store hooks
const { getProjectById } = useProject();
// derived values
@@ -27,6 +27,6 @@ const ProjectArchivedCyclesPage = observer(() => {
</div>
</>
);
});
}
export default ProjectArchivedCyclesPage;
export default observer(ProjectArchivedCyclesPage);
@@ -1,8 +1,6 @@
"use client";
import type { FC } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import { ArchiveIcon, CycleIcon, ModuleIcon, WorkItemsIcon } from "@plane/propel/icons";
import { Tooltip } from "@plane/propel/tooltip";
import { EIssuesStoreType } from "@plane/types";
@@ -20,13 +18,15 @@ import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs/project";
type TProps = {
activeTab: "issues" | "cycles" | "modules";
workspaceSlug: string;
projectId: string;
};
const PROJECT_ARCHIVES_BREADCRUMB_LIST: {
[key: string]: {
label: string;
href: string;
icon: React.FC<React.SVGAttributes<SVGElement> & { className?: string }>;
icon: React.FC<React.ComponentProps<"svg">>;
};
} = {
issues: {
@@ -46,11 +46,10 @@ const PROJECT_ARCHIVES_BREADCRUMB_LIST: {
},
};
export const ProjectArchivesHeader: FC<TProps> = observer((props: TProps) => {
const { activeTab } = props;
export const ProjectArchivesHeader: React.FC<TProps> = observer((props) => {
const { activeTab, workspaceSlug, projectId } = props;
// router
const router = useAppRouter();
const { workspaceSlug, projectId } = useParams();
// store hooks
const {
issues: { getGroupIssueCount },
@@ -69,7 +68,7 @@ export const ProjectArchivesHeader: FC<TProps> = observer((props: TProps) => {
<Header.LeftItem>
<div className="flex items-center gap-2.5">
<Breadcrumbs onBack={router.back} isLoading={loader === "init-loader"}>
<ProjectBreadcrumb workspaceSlug={workspaceSlug?.toString()} projectId={projectId?.toString()} />
<ProjectBreadcrumb workspaceSlug={workspaceSlug} projectId={projectId} />
<Breadcrumbs.Item
component={
<BreadcrumbLink
@@ -1,7 +1,7 @@
"use client";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import type { Route } from "./+types/page";
import useSWR from "swr";
// ui
import { Loader } from "@plane/ui";
@@ -13,9 +13,9 @@ import { IssueDetailRoot } from "@/components/issues/issue-detail";
import { useIssueDetail } from "@/hooks/store/use-issue-detail";
import { useProject } from "@/hooks/store/use-project";
const ArchivedIssueDetailsPage = observer(() => {
function ArchivedIssueDetailsPage({ params }: Route.ComponentProps) {
// router
const { workspaceSlug, projectId, archivedIssueId } = useParams();
const { workspaceSlug, projectId, archivedIssueId } = params;
// states
// hooks
const {
@@ -26,16 +26,12 @@ const ArchivedIssueDetailsPage = observer(() => {
const { getProjectById } = useProject();
const { isLoading } = useSWR(
workspaceSlug && projectId && archivedIssueId
? `ARCHIVED_ISSUE_DETAIL_${workspaceSlug}_${projectId}_${archivedIssueId}`
: null,
workspaceSlug && projectId && archivedIssueId
? () => fetchIssue(workspaceSlug.toString(), projectId.toString(), archivedIssueId.toString())
: null
`ARCHIVED_ISSUE_DETAIL_${workspaceSlug}_${projectId}_${archivedIssueId}`,
() => fetchIssue(workspaceSlug, projectId, archivedIssueId)
);
// derived values
const issue = archivedIssueId ? getIssueById(archivedIssueId.toString()) : undefined;
const issue = getIssueById(archivedIssueId);
const project = issue ? getProjectById(issue?.project_id ?? "") : undefined;
const pageTitle = project && issue ? `${project?.identifier}-${issue?.sequence_id} ${issue?.name}` : undefined;
@@ -64,19 +60,17 @@ const ArchivedIssueDetailsPage = observer(() => {
) : (
<div className="flex h-full overflow-hidden">
<div className="h-full w-full space-y-3 divide-y-2 divide-custom-border-200 overflow-y-auto">
{workspaceSlug && projectId && archivedIssueId && (
<IssueDetailRoot
workspaceSlug={workspaceSlug.toString()}
projectId={projectId.toString()}
issueId={archivedIssueId.toString()}
is_archived
/>
)}
<IssueDetailRoot
workspaceSlug={workspaceSlug}
projectId={projectId}
issueId={archivedIssueId}
is_archived
/>
</div>
</div>
)}
</>
);
});
}
export default ArchivedIssueDetailsPage;
export default observer(ArchivedIssueDetailsPage);
@@ -1,7 +1,6 @@
"use client";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import useSWR from "swr";
// ui
import { ArchiveIcon, WorkItemsIcon } from "@plane/propel/icons";
@@ -20,16 +19,20 @@ import { IssueService } from "@/services/issue";
const issueService = new IssueService();
export const ProjectArchivedIssueDetailsHeader = observer(() => {
// router
const { workspaceSlug, projectId, archivedIssueId } = useParams();
type ProjectArchivedIssueDetailsHeaderProps = {
workspaceSlug: string;
projectId: string;
archivedIssueId: string;
};
export const ProjectArchivedIssueDetailsHeader = observer(({ workspaceSlug, projectId, archivedIssueId }: ProjectArchivedIssueDetailsHeaderProps) => {
// store hooks
const { currentProjectDetails, loader } = useProject();
const { data: issueDetails } = useSWR(
workspaceSlug && projectId && archivedIssueId ? ISSUE_DETAILS(archivedIssueId.toString()) : null,
workspaceSlug && projectId && archivedIssueId ? ISSUE_DETAILS(archivedIssueId) : null,
workspaceSlug && projectId && archivedIssueId
? () => issueService.retrieve(workspaceSlug.toString(), projectId.toString(), archivedIssueId.toString())
? () => issueService.retrieve(workspaceSlug, projectId, archivedIssueId)
: null
);
@@ -37,7 +40,7 @@ export const ProjectArchivedIssueDetailsHeader = observer(() => {
<Header>
<Header.LeftItem>
<Breadcrumbs isLoading={loader === "init-loader"}>
<ProjectBreadcrumb workspaceSlug={workspaceSlug?.toString()} projectId={projectId?.toString()} />
<ProjectBreadcrumb workspaceSlug={workspaceSlug} projectId={projectId} />
<Breadcrumbs.Item
component={
<BreadcrumbLink
@@ -70,11 +73,7 @@ export const ProjectArchivedIssueDetailsHeader = observer(() => {
</Breadcrumbs>
</Header.LeftItem>
<Header.RightItem>
<IssueDetailQuickActions
workspaceSlug={workspaceSlug.toString()}
projectId={projectId.toString()}
issueId={archivedIssueId.toString()}
/>
<IssueDetailQuickActions workspaceSlug={workspaceSlug} projectId={projectId} issueId={archivedIssueId} />
</Header.RightItem>
</Header>
);
@@ -1,15 +1,29 @@
"use client";
import { Outlet } from "react-router";
import type { Route } from "./+types/layout";
// components
import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper";
import { ProjectArchivedIssueDetailsHeader } from "./header";
export default function ProjectArchivedIssueDetailLayout({ children }: { children: React.ReactNode }) {
export default function ProjectArchivedIssueDetailLayout({ params }: Route.ComponentProps) {
const { workspaceSlug, projectId, archivedIssueId } = params;
return (
<>
<AppHeader header={<ProjectArchivedIssueDetailsHeader />} />
<ContentWrapper>{children}</ContentWrapper>
<AppHeader
header={
<ProjectArchivedIssueDetailsHeader
workspaceSlug={workspaceSlug}
projectId={projectId}
archivedIssueId={archivedIssueId}
/>
}
/>
<ContentWrapper>
<Outlet />
</ContentWrapper>
</>
);
}
@@ -1,15 +1,23 @@
"use client";
import { Outlet } from "react-router";
import type { Route } from "./+types/layout";
// components
import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper";
import { ProjectArchivesHeader } from "../../header";
export default function ProjectArchiveIssuesLayout({ children }: { children: React.ReactNode }) {
export default function ProjectArchiveIssuesLayout({ params }: Route.ComponentProps) {
const { workspaceSlug, projectId } = params;
return (
<>
<AppHeader header={<ProjectArchivesHeader activeTab="issues" />} />
<ContentWrapper>{children}</ContentWrapper>
<AppHeader
header={<ProjectArchivesHeader activeTab="issues" workspaceSlug={workspaceSlug} projectId={projectId} />}
/>
<ContentWrapper>
<Outlet />
</ContentWrapper>
</>
);
}
@@ -1,7 +1,7 @@
"use client";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import type { Route } from "./+types/page";
// components
import { PageHead } from "@/components/core/page-title";
import { ArchivedIssuesHeader } from "@/components/issues/archived-issues-header";
@@ -9,13 +9,13 @@ import { ArchivedIssueLayoutRoot } from "@/components/issues/issue-layouts/roots
// hooks
import { useProject } from "@/hooks/store/use-project";
const ProjectArchivedIssuesPage = observer(() => {
function ProjectArchivedIssuesPage({ params }: Route.ComponentProps) {
// router
const { projectId } = useParams();
const { projectId } = params;
// store hooks
const { getProjectById } = useProject();
// derived values
const project = projectId ? getProjectById(projectId.toString()) : undefined;
const project = getProjectById(projectId);
const pageTitle = project?.name && `${project?.name} - Archived work items`;
return (
@@ -27,6 +27,6 @@ const ProjectArchivedIssuesPage = observer(() => {
</div>
</>
);
});
}
export default ProjectArchivedIssuesPage;
export default observer(ProjectArchivedIssuesPage);
@@ -1,15 +1,23 @@
"use client";
import { Outlet } from "react-router";
import type { Route } from "./+types/layout";
// components
import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper";
import { ProjectArchivesHeader } from "../header";
export default function ProjectArchiveModulesLayout({ children }: { children: React.ReactNode }) {
export default function ProjectArchiveModulesLayout({ params }: Route.ComponentProps) {
const { workspaceSlug, projectId } = params;
return (
<>
<AppHeader header={<ProjectArchivesHeader activeTab="modules" />} />
<ContentWrapper>{children}</ContentWrapper>
<AppHeader
header={<ProjectArchivesHeader activeTab="modules" workspaceSlug={workspaceSlug} projectId={projectId} />}
/>
<ContentWrapper>
<Outlet />
</ContentWrapper>
</>
);
}
@@ -1,20 +1,20 @@
"use client";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import type { Route } from "./+types/page";
// components
import { PageHead } from "@/components/core/page-title";
import { ArchivedModuleLayoutRoot, ArchivedModulesHeader } from "@/components/modules";
// hooks
import { useProject } from "@/hooks/store/use-project";
const ProjectArchivedModulesPage = observer(() => {
function ProjectArchivedModulesPage({ params }: Route.ComponentProps) {
// router
const { projectId } = useParams();
const { projectId } = params;
// store hooks
const { getProjectById } = useProject();
// derived values
const project = projectId ? getProjectById(projectId.toString()) : undefined;
const project = getProjectById(projectId);
const pageTitle = project?.name && `${project?.name} - Archived modules`;
return (
@@ -26,6 +26,6 @@ const ProjectArchivedModulesPage = observer(() => {
</div>
</>
);
});
}
export default ProjectArchivedModulesPage;
export default observer(ProjectArchivedModulesPage);
@@ -1,7 +1,7 @@
"use client";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import type { Route } from "./+types/page";
// plane imports
import { cn } from "@plane/utils";
// components
@@ -16,12 +16,12 @@ import { useProject } from "@/hooks/store/use-project";
import { useAppRouter } from "@/hooks/use-app-router";
import useLocalStorage from "@/hooks/use-local-storage";
// assets
import emptyCycle from "@/public/empty-state/cycle.svg";
import emptyCycle from "@/app/assets/empty-state/cycle.svg";
const CycleDetailPage = observer(() => {
function CycleDetailPage({ params }: Route.ComponentProps) {
// router
const router = useAppRouter();
const { workspaceSlug, projectId, cycleId } = useParams();
const { workspaceSlug, projectId, cycleId } = params;
// store hooks
const { getCycleById, loader } = useCycle();
const { getProjectById } = useProject();
@@ -30,14 +30,14 @@ const CycleDetailPage = observer(() => {
const { setValue, storedValue } = useLocalStorage("cycle_sidebar_collapsed", false);
useCyclesDetails({
workspaceSlug: workspaceSlug?.toString(),
projectId: projectId.toString(),
cycleId: cycleId.toString(),
workspaceSlug,
projectId,
cycleId,
});
// derived values
const isSidebarCollapsed = storedValue ? (storedValue === true ? true : false) : false;
const cycle = cycleId ? getCycleById(cycleId.toString()) : undefined;
const project = projectId ? getProjectById(projectId.toString()) : undefined;
const cycle = getCycleById(cycleId);
const project = getProjectById(projectId);
const pageTitle = project?.name && cycle?.name ? `${project?.name} - ${cycle?.name}` : undefined;
/**
@@ -78,9 +78,9 @@ const CycleDetailPage = observer(() => {
>
<CycleDetailsSidebar
handleClose={toggleSidebar}
cycleId={cycleId.toString()}
projectId={projectId.toString()}
workspaceSlug={workspaceSlug.toString()}
cycleId={cycleId}
projectId={projectId}
workspaceSlug={workspaceSlug}
/>
</div>
)}
@@ -89,6 +89,6 @@ const CycleDetailPage = observer(() => {
)}
</>
);
});
}
export default CycleDetailPage;
export default observer(CycleDetailPage);
@@ -1,16 +1,19 @@
"use client";
import { Outlet } from "react-router";
// components
import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper";
import { CycleIssuesHeader } from "./header";
import { CycleIssuesMobileHeader } from "./mobile-header";
export default function ProjectCycleIssuesLayout({ children }: { children: React.ReactNode }) {
export default function ProjectCycleIssuesLayout() {
return (
<>
<AppHeader header={<CycleIssuesHeader />} mobileHeader={<CycleIssuesMobileHeader />} />
<ContentWrapper>{children}</ContentWrapper>
<ContentWrapper>
<Outlet />
</ContentWrapper>
</>
);
}
@@ -2,7 +2,6 @@
import type { FC } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// ui
import { EProjectFeatureKey, EUserPermissions, EUserPermissionsLevel, CYCLE_TRACKER_ELEMENTS } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
@@ -17,12 +16,14 @@ import { useUserPermissions } from "@/hooks/store/user";
import { useAppRouter } from "@/hooks/use-app-router";
// plane web
import { CommonProjectBreadcrumbs } from "@/plane-web/components/breadcrumbs/common";
// constants
export const CyclesListHeader: FC = observer(() => {
type Props = {
workspaceSlug: string;
};
export const CyclesListHeader: FC<Props> = observer(({ workspaceSlug }) => {
// router
const router = useAppRouter();
const { workspaceSlug } = useParams();
// store hooks
const { toggleCreateCycleModal } = useCommandPalette();
@@ -40,7 +41,7 @@ export const CyclesListHeader: FC = observer(() => {
<Header.LeftItem>
<Breadcrumbs onBack={router.back} isLoading={loader === "init-loader"}>
<CommonProjectBreadcrumbs
workspaceSlug={workspaceSlug?.toString()}
workspaceSlug={workspaceSlug}
projectId={currentProjectDetails?.id ?? ""}
featureKey={EProjectFeatureKey.CYCLES}
isLast
@@ -1,16 +1,20 @@
"use client";
// components
import { Outlet } from "react-router";
import type { Route } from "../../../+types/layout";
import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper";
import { CyclesListHeader } from "./header";
import { CyclesListMobileHeader } from "./mobile-header";
export default function ProjectCyclesListLayout({ children }: { children: React.ReactNode }) {
export default function ProjectCyclesListLayout({ params }: Route.ComponentProps) {
const { workspaceSlug } = params;
return (
<>
<AppHeader header={<CyclesListHeader />} mobileHeader={<CyclesListMobileHeader />} />
<ContentWrapper>{children}</ContentWrapper>
<AppHeader header={<CyclesListHeader workspaceSlug={workspaceSlug} />} />
<ContentWrapper>
<Outlet />
</ContentWrapper>
</>
);
}
@@ -2,7 +2,7 @@
import { useState } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import type { Route } from "./+types/page";
// plane imports
import { EUserPermissionsLevel, CYCLE_TRACKER_ELEMENTS } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
@@ -26,7 +26,7 @@ import { useUserPermissions } from "@/hooks/store/user";
import { useAppRouter } from "@/hooks/use-app-router";
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
const ProjectCyclesPage = observer(() => {
const ProjectCyclesPage: React.FC<Route.ComponentProps> = observer(({ params }) => {
// states
const [createModal, setCreateModal] = useState(false);
// store hooks
@@ -34,7 +34,7 @@ const ProjectCyclesPage = observer(() => {
const { getProjectById, currentProjectDetails } = useProject();
// router
const router = useAppRouter();
const { workspaceSlug, projectId } = useParams();
const { workspaceSlug, projectId } = params;
// plane hooks
const { t } = useTranslation();
// cycle filters hook
@@ -1,15 +1,18 @@
"use client";
// components
import { Outlet } from "react-router";
import type { Route } from "./+types/layout";
import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper";
import { ProjectInboxHeader } from "@/plane-web/components/projects/settings/intake/header";
export default function ProjectInboxIssuesLayout({ children }: { children: React.ReactNode }) {
export default function ProjectInboxIssuesLayout() {
return (
<>
<AppHeader header={<ProjectInboxHeader />} />
<ContentWrapper>{children}</ContentWrapper>
<ContentWrapper>
<Outlet />
</ContentWrapper>
</>
);
}
@@ -1,6 +1,7 @@
"use client";
import { observer } from "mobx-react";
import { useParams, useSearchParams } from "next/navigation";
import type { Route } from "./+types/page";
import { useSearchParams } from "next/navigation";
// plane imports
import { EUserPermissionsLevel } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
@@ -15,10 +16,10 @@ import { useUserPermissions } from "@/hooks/store/user";
import { useAppRouter } from "@/hooks/use-app-router";
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
const ProjectInboxPage = observer(() => {
const ProjectInboxPage: React.FC<Route.ComponentProps> = observer(({ params }) => {
/// router
const router = useAppRouter();
const { workspaceSlug, projectId } = useParams();
const { workspaceSlug, projectId } = params;
const searchParams = useSearchParams();
const navigationTab = searchParams.get("currentTab");
const inboxIssueId = searchParams.get("inboxIssueId");
@@ -2,7 +2,7 @@
import { useEffect } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import type { Route } from "./+types/page";
import { useTheme } from "next-themes";
import useSWR from "swr";
import { useTranslation } from "@plane/i18n";
@@ -12,17 +12,17 @@ import { LogoSpinner } from "@/components/common/logo-spinner";
// hooks
import { useAppRouter } from "@/hooks/use-app-router";
// assets
import emptyIssueDark from "@/public/empty-state/search/issues-dark.webp";
import emptyIssueLight from "@/public/empty-state/search/issues-light.webp";
import emptyIssueDark from "@/app/assets/empty-state/search/issues-dark.webp";
import emptyIssueLight from "@/app/assets/empty-state/search/issues-light.webp";
// services
import { IssueService } from "@/services/issue/issue.service";
const issueService = new IssueService();
const IssueDetailsPage = observer(() => {
const IssueDetailsPage: React.FC<Route.ComponentProps> = observer(({ params }) => {
const router = useAppRouter();
const { t } = useTranslation();
const { workspaceSlug, projectId, issueId } = useParams();
const { workspaceSlug, projectId, issueId } = params;
const { resolvedTheme } = useTheme();
const { data, isLoading, error } = useSWR(
@@ -1,16 +1,18 @@
"use client";
import { Outlet } from "react-router";
import type { Route } from "../../../+types/layout";
// components
import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper";
import { ProjectIssuesHeader } from "./header";
import { ProjectIssuesMobileHeader } from "./mobile-header";
export default function ProjectIssuesLayout({ children }: { children: React.ReactNode }) {
export default function ProjectIssuesLayout() {
return (
<>
<AppHeader header={<ProjectIssuesHeader />} mobileHeader={<ProjectIssuesMobileHeader />} />
<ContentWrapper>{children}</ContentWrapper>
<ContentWrapper>
<Outlet />
</ContentWrapper>
</>
);
}
@@ -2,7 +2,7 @@
import { observer } from "mobx-react";
import Head from "next/head";
import { useParams } from "next/navigation";
import type { Route } from "./+types/page";
// i18n
import { useTranslation } from "@plane/i18n";
// components
@@ -11,8 +11,8 @@ import { ProjectLayoutRoot } from "@/components/issues/issue-layouts/roots/proje
// hooks
import { useProject } from "@/hooks/store/use-project";
const ProjectIssuesPage = observer(() => {
const { projectId } = useParams();
const ProjectIssuesPage: React.FC<Route.ComponentProps> = observer(({ params }) => {
const { projectId } = params;
// i18n
const { t } = useTranslation();
// store
@@ -1,7 +1,7 @@
"use client";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import type { Route } from "./+types/page";
import useSWR from "swr";
// components
import { cn } from "@plane/utils";
@@ -16,12 +16,12 @@ import { useProject } from "@/hooks/store/use-project";
import { useAppRouter } from "@/hooks/use-app-router";
import useLocalStorage from "@/hooks/use-local-storage";
// assets
import emptyModule from "@/public/empty-state/module.svg";
import emptyModule from "@/app/assets/empty-state/module.svg";
const ModuleIssuesPage = observer(() => {
const ModuleIssuesPage: React.FC<Route.ComponentProps> = observer(({ params }) => {
// router
const router = useAppRouter();
const { workspaceSlug, projectId, moduleId } = useParams();
const { workspaceSlug, projectId, moduleId } = params;
// store hooks
const { fetchModuleDetails, getModuleById } = useModule();
const { getProjectById } = useProject();
@@ -2,7 +2,6 @@
import { useCallback, useRef, useState } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// icons
import { ChartNoAxesColumn, PanelRight, SlidersHorizontal } from "lucide-react";
// plane imports
@@ -45,15 +44,19 @@ import { usePlatformOS } from "@/hooks/use-platform-os";
// plane web
import { CommonProjectBreadcrumbs } from "@/plane-web/components/breadcrumbs/common";
export const ModuleIssuesHeader: React.FC = observer(() => {
type Props = {
workspaceSlug: string;
projectId: string;
moduleId: string;
};
export const ModuleIssuesHeader: React.FC<Props> = observer(({ workspaceSlug, projectId, moduleId }) => {
// refs
const parentRef = useRef<HTMLDivElement>(null);
// states
const [analyticsModal, setAnalyticsModal] = useState(false);
// router
const router = useAppRouter();
const { workspaceSlug, projectId, moduleId: routerModuleId } = useParams();
const moduleId = routerModuleId ? routerModuleId.toString() : undefined;
// hooks
const { isMobile } = usePlatformOS();
// store hooks
@@ -84,24 +87,21 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
const handleLayoutChange = useCallback(
(layout: EIssueLayoutTypes) => {
if (!projectId) return;
updateFilters(projectId.toString(), EIssueFilterType.DISPLAY_FILTERS, { layout: layout });
updateFilters(projectId, EIssueFilterType.DISPLAY_FILTERS, { layout: layout });
},
[projectId, updateFilters]
);
const handleDisplayFilters = useCallback(
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
if (!projectId) return;
updateFilters(projectId.toString(), EIssueFilterType.DISPLAY_FILTERS, updatedDisplayFilter);
updateFilters(projectId, EIssueFilterType.DISPLAY_FILTERS, updatedDisplayFilter);
},
[projectId, updateFilters]
);
const handleDisplayProperties = useCallback(
(property: Partial<IIssueDisplayProperties>) => {
if (!projectId) return;
updateFilters(projectId.toString(), EIssueFilterType.DISPLAY_PROPERTIES, property);
updateFilters(projectId, EIssueFilterType.DISPLAY_PROPERTIES, property);
},
[projectId, updateFilters]
);
@@ -131,14 +131,14 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
<div className="flex items-center gap-2">
<Breadcrumbs onBack={router.back} isLoading={loader === "init-loader"}>
<CommonProjectBreadcrumbs
workspaceSlug={workspaceSlug?.toString() ?? ""}
projectId={projectId?.toString() ?? ""}
workspaceSlug={workspaceSlug}
projectId={projectId}
featureKey={EProjectFeatureKey.MODULES}
/>
<Breadcrumbs.Item
component={
<BreadcrumbNavigationSearchDropdown
selectedItem={moduleId?.toString() ?? ""}
selectedItem={moduleId}
navigationItems={switcherOptions}
onChange={(value: string) => {
router.push(`/${workspaceSlug}/projects/${projectId}/modules/${value}`);
@@ -252,8 +252,8 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
<ModuleQuickActions
parentRef={parentRef}
moduleId={moduleId}
projectId={projectId.toString()}
workspaceSlug={workspaceSlug.toString()}
projectId={projectId}
workspaceSlug={workspaceSlug}
customClassName="flex-shrink-0 flex items-center justify-center bg-custom-background-80/70 rounded size-[26px]"
/>
)}
@@ -1,16 +1,25 @@
"use client";
import { Outlet } from "react-router";
import type { Route } from "./+types/layout";
// components
import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper";
import { ModuleIssuesHeader } from "./header";
import { ModuleIssuesMobileHeader } from "./mobile-header";
export default function ProjectModuleIssuesLayout({ children }: { children: React.ReactNode }) {
export default function ProjectModuleIssuesLayout({ params }: Route.ComponentProps) {
const { workspaceSlug, projectId, moduleId } = params;
return (
<>
<AppHeader header={<ModuleIssuesHeader />} mobileHeader={<ModuleIssuesMobileHeader />} />
<ContentWrapper>{children}</ContentWrapper>
<AppHeader
header={<ModuleIssuesHeader workspaceSlug={workspaceSlug} projectId={projectId} moduleId={moduleId} />}
mobileHeader={
<ModuleIssuesMobileHeader workspaceSlug={workspaceSlug} projectId={projectId} moduleId={moduleId} />
}
/>
<ContentWrapper>
<Outlet />
</ContentWrapper>
</>
);
}
@@ -2,7 +2,6 @@
import { useCallback, useState } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// icons
import { Calendar, ChevronDown, Kanban, List } from "lucide-react";
// plane imports
@@ -26,13 +25,13 @@ const SUPPORTED_LAYOUTS = [
{ key: "calendar", i18n_title: "issue.layouts.calendar", icon: Calendar },
];
export const ModuleIssuesMobileHeader = observer(() => {
// router
const { workspaceSlug, projectId, moduleId } = useParams() as {
workspaceSlug: string;
projectId: string;
moduleId: string;
};
type Props = {
workspaceSlug: string;
projectId: string;
moduleId: string;
};
export const ModuleIssuesMobileHeader = observer(({ workspaceSlug, projectId, moduleId }: Props) => {
// states
const [analyticsModal, setAnalyticsModal] = useState(false);
// plane hooks
@@ -45,11 +44,10 @@ export const ModuleIssuesMobileHeader = observer(() => {
} = useIssues(EIssuesStoreType.MODULE);
// derived values
const activeLayout = issueFilters?.displayFilters?.layout;
const moduleDetails = moduleId ? getModuleById(moduleId.toString()) : undefined;
const moduleDetails = getModuleById(moduleId);
const handleLayoutChange = useCallback(
(layout: EIssueLayoutTypes) => {
if (!workspaceSlug || !projectId) return;
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, { layout: layout }, moduleId);
},
[workspaceSlug, projectId, moduleId, updateFilters]
@@ -57,7 +55,6 @@ export const ModuleIssuesMobileHeader = observer(() => {
const handleDisplayFilters = useCallback(
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
if (!workspaceSlug || !projectId) return;
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, updatedDisplayFilter, moduleId);
},
[workspaceSlug, projectId, moduleId, updateFilters]
@@ -65,7 +62,6 @@ export const ModuleIssuesMobileHeader = observer(() => {
const handleDisplayProperties = useCallback(
(property: Partial<IIssueDisplayProperties>) => {
if (!workspaceSlug || !projectId) return;
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_PROPERTIES, property, moduleId);
},
[workspaceSlug, projectId, moduleId, updateFilters]
@@ -1,7 +1,6 @@
"use client";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// plane imports
import { EProjectFeatureKey, EUserPermissions, EUserPermissionsLevel, MODULE_TRACKER_ELEMENTS } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
@@ -19,10 +18,14 @@ import { useAppRouter } from "@/hooks/use-app-router";
import { CommonProjectBreadcrumbs } from "@/plane-web/components/breadcrumbs/common";
// constants
export const ModulesListHeader: React.FC = observer(() => {
type Props = {
workspaceSlug: string;
projectId: string;
};
export const ModulesListHeader: React.FC<Props> = observer(({ workspaceSlug, projectId }) => {
// router
const router = useAppRouter();
const { workspaceSlug, projectId } = useParams() as { workspaceSlug: string; projectId: string };
// store hooks
const { toggleCreateModuleModal } = useCommandPalette();
const { allowPermissions } = useUserPermissions();
@@ -43,8 +46,8 @@ export const ModulesListHeader: React.FC = observer(() => {
<div>
<Breadcrumbs onBack={router.back} isLoading={loader === "init-loader"}>
<CommonProjectBreadcrumbs
workspaceSlug={workspaceSlug?.toString() ?? ""}
projectId={projectId?.toString() ?? ""}
workspaceSlug={workspaceSlug}
projectId={projectId}
featureKey={EProjectFeatureKey.MODULES}
isLast
/>
@@ -1,16 +1,23 @@
"use client";
import { Outlet } from "react-router";
import type { Route } from "../../../+types/layout";
// components
import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper";
import { ModulesListHeader } from "./header";
import { ModulesListMobileHeader } from "./mobile-header";
export default function ProjectModulesListLayout({ children }: { children: React.ReactNode }) {
export default function ProjectModulesListLayout({ params }: Route.ComponentProps) {
const { workspaceSlug, projectId } = params;
return (
<>
<AppHeader header={<ModulesListHeader />} mobileHeader={<ModulesListMobileHeader />} />
<ContentWrapper>{children}</ContentWrapper>
<AppHeader
header={<ModulesListHeader workspaceSlug={workspaceSlug} projectId={projectId} />}
mobileHeader={<ModulesListMobileHeader workspaceSlug={workspaceSlug} projectId={projectId} />}
/>
<ContentWrapper>
<Outlet />
</ContentWrapper>
</>
);
}
@@ -9,7 +9,12 @@ import { ModuleLayoutIcon } from "@/components/modules";
import { useModuleFilter } from "@/hooks/store/use-module-filter";
import { useProject } from "@/hooks/store/use-project";
export const ModulesListMobileHeader = observer(() => {
type Props = {
workspaceSlug: string;
projectId: string;
};
export const ModulesListMobileHeader = observer(({ workspaceSlug, projectId }: Props) => {
const { currentProjectDetails } = useProject();
const { updateDisplayFilters } = useModuleFilter();
const { t } = useTranslation();
@@ -2,7 +2,7 @@
import { useCallback } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import type { Route } from "./+types/page";
// types
import { EUserPermissionsLevel } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
@@ -21,10 +21,10 @@ import { useUserPermissions } from "@/hooks/store/user";
import { useAppRouter } from "@/hooks/use-app-router";
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
const ProjectModulesPage = observer(() => {
const ProjectModulesPage: React.FC<Route.ComponentProps> = observer(({ params }) => {
// router
const router = useAppRouter();
const { workspaceSlug, projectId } = useParams();
const { workspaceSlug, projectId } = params;
// plane hooks
const { t } = useTranslation();
// store
@@ -3,7 +3,7 @@
import { useCallback, useEffect, useMemo } from "react";
import { observer } from "mobx-react";
import Link from "next/link";
import { useParams } from "next/navigation";
import type { Route } from "./+types/page";
import useSWR from "swr";
// plane types
import { getButtonStyling } from "@plane/propel/button";
@@ -35,10 +35,10 @@ const projectPageVersionService = new ProjectPageVersionService();
const storeType = EPageStoreType.PROJECT;
const PageDetailsPage = observer(() => {
const PageDetailsPage: React.FC<Route.ComponentProps> = observer(({ params }) => {
const { workspaceSlug, projectId, pageId } = params;
// router
const router = useAppRouter();
const { workspaceSlug, projectId, pageId } = useParams();
// store hooks
const { createPage, fetchPageDetails } = usePageStore(storeType);
const page = usePage({
@@ -28,19 +28,24 @@ export interface IPagesHeaderProps {
const storeType = EPageStoreType.PROJECT;
export const PageDetailsHeader = observer(() => {
type Props = {
workspaceSlug: string;
projectId: string;
pageId: string;
};
export const PageDetailsHeader = observer(({ workspaceSlug, projectId, pageId }: Props) => {
// router
const router = useAppRouter();
const { workspaceSlug, pageId, projectId } = useParams();
// store hooks
const { loader } = useProject();
const { getPageById, getCurrentProjectPageIds } = usePageStore(storeType);
const page = usePage({
pageId: pageId?.toString() ?? "",
pageId: pageId,
storeType,
});
// derived values
const projectPageIds = getCurrentProjectPageIds(projectId?.toString());
const projectPageIds = getCurrentProjectPageIds(projectId);
const switcherOptions = projectPageIds
.map((id) => {
@@ -67,15 +72,15 @@ export const PageDetailsHeader = observer(() => {
<div>
<Breadcrumbs isLoading={loader === "init-loader"}>
<CommonProjectBreadcrumbs
workspaceSlug={workspaceSlug?.toString()}
projectId={projectId?.toString()}
workspaceSlug={workspaceSlug}
projectId={projectId}
featureKey={EProjectFeatureKey.PAGES}
/>
<Breadcrumbs.Item
component={
<BreadcrumbNavigationSearchDropdown
selectedItem={pageId?.toString() ?? ""}
selectedItem={pageId}
navigationItems={switcherOptions}
onChange={(value: string) => {
router.push(`/${workspaceSlug}/projects/${projectId}/pages/${value}`);
@@ -1,7 +1,7 @@
"use client";
import { Outlet } from "react-router";
import type { Route } from "./+types/layout";
// component
import { useParams } from "next/navigation";
// import { useParams } from "react-router";
import useSWR from "swr";
import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper";
@@ -10,18 +10,20 @@ import { EPageStoreType, usePageStore } from "@/plane-web/hooks/store";
// local components
import { PageDetailsHeader } from "./header";
export default function ProjectPageDetailsLayout({ children }: { children: React.ReactNode }) {
const { workspaceSlug, projectId } = useParams();
export default function ProjectPageDetailsLayout({ params }: Route.ComponentProps) {
const { workspaceSlug, projectId, pageId } = params;
const { fetchPagesList } = usePageStore(EPageStoreType.PROJECT);
// fetching pages list
useSWR(
workspaceSlug && projectId ? `PROJECT_PAGES_${projectId}` : null,
workspaceSlug && projectId ? () => fetchPagesList(workspaceSlug.toString(), projectId.toString()) : null
workspaceSlug && projectId ? () => fetchPagesList(workspaceSlug, projectId) : null
);
return (
<>
<AppHeader header={<PageDetailsHeader />} />
<ContentWrapper>{children}</ContentWrapper>
<AppHeader header={<PageDetailsHeader workspaceSlug={workspaceSlug} projectId={projectId} pageId={pageId} />} />
<ContentWrapper>
<Outlet />
</ContentWrapper>
</>
);
}
@@ -2,7 +2,7 @@
import { useState } from "react";
import { observer } from "mobx-react";
import { useParams, useRouter, useSearchParams } from "next/navigation";
import { useRouter, useSearchParams } from "next/navigation";
// constants
import {
EPageAccess,
@@ -25,12 +25,16 @@ import { CommonProjectBreadcrumbs } from "@/plane-web/components/breadcrumbs/com
// plane web hooks
import { EPageStoreType, usePageStore } from "@/plane-web/hooks/store";
export const PagesListHeader = observer(() => {
type Props = {
workspaceSlug: string;
projectId: string;
};
export const PagesListHeader = observer(({ workspaceSlug, projectId }: Props) => {
// states
const [isCreatingPage, setIsCreatingPage] = useState(false);
// router
const router = useRouter();
const { workspaceSlug } = useParams();
const searchParams = useSearchParams();
const pageType = searchParams.get("type");
// store hooks
@@ -53,7 +57,7 @@ export const PagesListHeader = observer(() => {
state: "SUCCESS",
},
});
const pageId = `/${workspaceSlug}/projects/${currentProjectDetails?.id}/pages/${res?.id}`;
const pageId = `/${workspaceSlug}/projects/${projectId}/pages/${res?.id}`;
router.push(pageId);
})
.catch((err) => {
@@ -77,8 +81,8 @@ export const PagesListHeader = observer(() => {
<Header.LeftItem>
<Breadcrumbs isLoading={loader === "init-loader"}>
<CommonProjectBreadcrumbs
workspaceSlug={workspaceSlug?.toString() ?? ""}
projectId={currentProjectDetails?.id?.toString() ?? ""}
workspaceSlug={workspaceSlug}
projectId={projectId}
featureKey={EProjectFeatureKey.PAGES}
isLast
/>
@@ -1,17 +1,20 @@
"use client";
import type { ReactNode } from "react";
import { Outlet } from "react-router";
import type { Route } from "../../../+types/layout";
// components
import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper";
// local components
import { PagesListHeader } from "./header";
export default function ProjectPagesListLayout({ children }: { children: ReactNode }) {
export default function ProjectPagesListLayout({ params }: Route.ComponentProps) {
const { workspaceSlug, projectId } = params;
return (
<>
<AppHeader header={<PagesListHeader />} />
<ContentWrapper>{children}</ContentWrapper>
<AppHeader header={<PagesListHeader workspaceSlug={workspaceSlug} projectId={projectId} />} />
<ContentWrapper>
<Outlet />
</ContentWrapper>
</>
);
}
@@ -1,7 +1,8 @@
"use client";
import { observer } from "mobx-react";
import { useParams, useSearchParams } from "next/navigation";
import type { Route } from "./+types/page";
import { useSearchParams } from "next/navigation";
// plane imports
import { EUserPermissionsLevel } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
@@ -20,12 +21,12 @@ import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
// plane web hooks
import { EPageStoreType } from "@/plane-web/hooks/store";
const ProjectPagesPage = observer(() => {
const ProjectPagesPage: React.FC<Route.ComponentProps> = observer(({ params }) => {
// router
const router = useAppRouter();
const searchParams = useSearchParams();
const type = searchParams.get("type");
const { workspaceSlug, projectId } = useParams();
const { workspaceSlug, projectId } = params;
// plane hooks
const { t } = useTranslation();
// store hooks
@@ -2,7 +2,6 @@
import { useCallback, useRef } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import { Lock } from "lucide-react";
// plane constants
import {
@@ -37,13 +36,17 @@ import { useUserPermissions } from "@/hooks/store/user";
import { useAppRouter } from "@/hooks/use-app-router";
import { CommonProjectBreadcrumbs } from "@/plane-web/components/breadcrumbs/common";
export const ProjectViewIssuesHeader: React.FC = observer(() => {
type Props = {
workspaceSlug: string;
projectId: string;
viewId: string;
};
export const ProjectViewIssuesHeader: React.FC<Props> = observer(({ workspaceSlug, projectId, viewId }) => {
// refs
const parentRef = useRef(null);
// router
const router = useAppRouter();
const { workspaceSlug, projectId, viewId: routerViewId } = useParams();
const viewId = routerViewId ? routerViewId.toString() : undefined;
// store hooks
const {
issuesFilter: { issueFilters, updateFilters },
@@ -58,47 +61,26 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
const handleLayoutChange = useCallback(
(layout: EIssueLayoutTypes) => {
if (!workspaceSlug || !projectId || !viewId) return;
updateFilters(
workspaceSlug.toString(),
projectId.toString(),
EIssueFilterType.DISPLAY_FILTERS,
{ layout: layout },
viewId.toString()
);
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, { layout: layout }, viewId);
},
[workspaceSlug, projectId, viewId, updateFilters]
);
const handleDisplayFilters = useCallback(
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
if (!workspaceSlug || !projectId || !viewId) return;
updateFilters(
workspaceSlug.toString(),
projectId.toString(),
EIssueFilterType.DISPLAY_FILTERS,
updatedDisplayFilter,
viewId.toString()
);
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, updatedDisplayFilter, viewId);
},
[workspaceSlug, projectId, viewId, updateFilters]
);
const handleDisplayProperties = useCallback(
(property: Partial<IIssueDisplayProperties>) => {
if (!workspaceSlug || !projectId || !viewId) return;
updateFilters(
workspaceSlug.toString(),
projectId.toString(),
EIssueFilterType.DISPLAY_PROPERTIES,
property,
viewId.toString()
);
updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_PROPERTIES, property, viewId);
},
[workspaceSlug, projectId, viewId, updateFilters]
);
const viewDetails = viewId ? getViewById(viewId.toString()) : null;
const viewDetails = getViewById(viewId);
const canUserCreateIssue = allowPermissions(
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
@@ -124,15 +106,15 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
<Header.LeftItem>
<Breadcrumbs isLoading={loader === "init-loader"}>
<CommonProjectBreadcrumbs
workspaceSlug={workspaceSlug?.toString() ?? ""}
projectId={projectId?.toString() ?? ""}
workspaceSlug={workspaceSlug}
projectId={projectId}
featureKey={EProjectFeatureKey.VIEWS}
/>
<Breadcrumbs.Item
component={
<BreadcrumbNavigationSearchDropdown
selectedItem={viewId?.toString() ?? ""}
selectedItem={viewId}
navigationItems={switcherOptions}
onChange={(value: string) => {
router.push(`/${workspaceSlug}/projects/${projectId}/views/${value}`);
@@ -208,9 +190,9 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
<ViewQuickActions
parentRef={parentRef}
customClassName="flex-shrink-0 flex items-center justify-center size-[26px] bg-custom-background-80/70 rounded"
projectId={projectId.toString()}
projectId={projectId}
view={viewDetails}
workspaceSlug={workspaceSlug.toString()}
workspaceSlug={workspaceSlug}
/>
</div>
</Header.RightItem>
@@ -1,7 +1,7 @@
"use client";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import type { Route } from "./+types/page";
import useSWR from "swr";
// components
import { EmptyState } from "@/components/common/empty-state";
@@ -12,12 +12,12 @@ import { useProject } from "@/hooks/store/use-project";
import { useProjectView } from "@/hooks/store/use-project-view";
// assets
import { useAppRouter } from "@/hooks/use-app-router";
import emptyView from "@/public/empty-state/view.svg";
import emptyView from "@/app/assets/empty-state/view.svg";
const ProjectViewIssuesPage = observer(() => {
const ProjectViewIssuesPage: React.FC<Route.ComponentProps> = observer(({ params }) => {
// router
const router = useAppRouter();
const { workspaceSlug, projectId, viewId } = useParams();
const { workspaceSlug, projectId, viewId } = params;
// store hooks
const { fetchViewDetails, getViewById } = useProjectView();
const { getProjectById } = useProject();
@@ -1,15 +1,21 @@
"use client";
import { Outlet } from "react-router";
import type { Route } from "./+types/layout";
import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper";
// local components
import { ProjectViewIssuesHeader } from "./[viewId]/header";
export default function ProjectViewIssuesLayout({ children }: { children: React.ReactNode }) {
export default function ProjectViewIssuesLayout({ params }: Route.ComponentProps) {
const { workspaceSlug, projectId, viewId } = params;
return (
<>
<AppHeader header={<ProjectViewIssuesHeader />} />
<ContentWrapper>{children}</ContentWrapper>
<AppHeader
header={<ProjectViewIssuesHeader workspaceSlug={workspaceSlug} projectId={projectId} viewId={viewId} />}
/>
<ContentWrapper>
<Outlet />
</ContentWrapper>
</>
);
}
@@ -1,7 +1,6 @@
"use client";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// ui
import { EProjectFeatureKey, PROJECT_VIEW_TRACKER_ELEMENTS } from "@plane/constants";
import { Button } from "@plane/propel/button";
@@ -14,8 +13,12 @@ import { useProject } from "@/hooks/store/use-project";
// plane web
import { CommonProjectBreadcrumbs } from "@/plane-web/components/breadcrumbs/common";
export const ProjectViewsHeader = observer(() => {
const { workspaceSlug, projectId } = useParams() as { workspaceSlug: string; projectId: string };
type Props = {
workspaceSlug: string;
projectId: string;
};
export const ProjectViewsHeader = observer(({ workspaceSlug, projectId }: Props) => {
// store hooks
const { toggleCreateViewModal } = useCommandPalette();
const { loader } = useProject();
@@ -26,8 +29,8 @@ export const ProjectViewsHeader = observer(() => {
<Header.LeftItem>
<Breadcrumbs isLoading={loader === "init-loader"}>
<CommonProjectBreadcrumbs
workspaceSlug={workspaceSlug?.toString() ?? ""}
projectId={projectId?.toString() ?? ""}
workspaceSlug={workspaceSlug}
projectId={projectId}
featureKey={EProjectFeatureKey.VIEWS}
isLast
/>
@@ -1,16 +1,23 @@
"use client";
import { Outlet } from "react-router";
import type { Route } from "../../../+types/layout";
import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper";
// local components
import { ProjectViewsHeader } from "./header";
import { ViewMobileHeader } from "./mobile-header";
export default function ProjectViewsListLayout({ children }: { children: React.ReactNode }) {
export default function ProjectViewsListLayout({ params }: Route.ComponentProps) {
const { workspaceSlug, projectId } = params;
return (
<>
<AppHeader header={<ProjectViewsHeader />} mobileHeader={<ViewMobileHeader />} />
<ContentWrapper>{children}</ContentWrapper>
<AppHeader
header={<ProjectViewsHeader workspaceSlug={workspaceSlug} projectId={projectId} />}
mobileHeader={<ViewMobileHeader workspaceSlug={workspaceSlug} projectId={projectId} />}
/>
<ContentWrapper>
<Outlet />
</ContentWrapper>
</>
);
}
@@ -12,7 +12,12 @@ import { ViewOrderByDropdown } from "@/components/views/filters/order-by";
import { useMember } from "@/hooks/store/use-member";
import { useProjectView } from "@/hooks/store/use-project-view";
export const ViewMobileHeader = observer(() => {
type Props = {
workspaceSlug: string;
projectId: string;
};
export const ViewMobileHeader = observer(({ workspaceSlug, projectId }: Props) => {
// store hooks
const { filters, updateFilters } = useProjectView();
const {
@@ -2,7 +2,7 @@
import { useCallback } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import type { Route } from "./+types/page";
// components
import { EUserPermissionsLevel } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
@@ -23,10 +23,10 @@ import { useUserPermissions } from "@/hooks/store/user";
import { useAppRouter } from "@/hooks/use-app-router";
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
const ProjectViewsPage = observer(() => {
const ProjectViewsPage: React.FC<Route.ComponentProps> = observer(({ params }) => {
// router
const router = useAppRouter();
const { workspaceSlug, projectId } = useParams();
const { workspaceSlug, projectId } = params;
// plane hooks
const { t } = useTranslation();
// store
@@ -1,17 +1,19 @@
"use client";
import type { ReactNode } from "react";
// components
import { Outlet } from "react-router";
import type { Route } from "../+types/layout";
import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper";
// 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 }) {
export default function ProjectListLayout() {
return (
<>
<AppHeader header={<ProjectsListHeader />} mobileHeader={<ProjectsListMobileHeader />} />
<ContentWrapper>{children}</ContentWrapper>
<ContentWrapper>
<Outlet />
</ContentWrapper>
</>
);
}
@@ -1,16 +1,14 @@
"use client";
import type { ReactNode } from "react";
import { useParams } from "next/navigation";
// plane web layouts
import { Outlet } from "react-router";
import type { Route } from "./+types/layout";
import { ProjectAuthWrapper } from "@/plane-web/layouts/project-wrapper";
const ProjectDetailLayout = ({ children }: { children: ReactNode }) => {
// router
const { workspaceSlug, projectId } = useParams();
const ProjectDetailLayout: React.FC<Route.ComponentProps> = ({ params }) => {
const { workspaceSlug, projectId } = params;
return (
<ProjectAuthWrapper workspaceSlug={workspaceSlug?.toString()} projectId={projectId?.toString()}>
{children}
<ProjectAuthWrapper workspaceSlug={workspaceSlug!} projectId={projectId!}>
<Outlet />
</ProjectAuthWrapper>
);
};
@@ -1,17 +1,19 @@
"use client";
import type { ReactNode } from "react";
// components
import { Outlet } from "react-router";
import type { Route } from "./+types/layout";
import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper";
// 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 }) {
export default function ProjectListLayout() {
return (
<>
<AppHeader header={<ProjectsListHeader />} mobileHeader={<ProjectsListMobileHeader />} />
<ContentWrapper>{children}</ContentWrapper>
<ContentWrapper>
<Outlet />
</ContentWrapper>
</>
);
}
@@ -8,8 +8,8 @@ import { useTranslation } from "@plane/i18n";
// helpers
import { captureElementAndEvent } from "@/helpers/event-tracker.helper";
// public imports
import githubBlackImage from "@/public/logos/github-black.png";
import githubWhiteImage from "@/public/logos/github-white.png";
import githubBlackImage from "@/app/assets/logos/github-black.png";
import githubWhiteImage from "@/app/assets/logos/github-white.png";
export const StarUsOnGitHubLink = () => {
// plane hooks
@@ -1,7 +1,6 @@
"use client";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// plane imports
import { Button } from "@plane/propel/button";
import { RecentStickyIcon } from "@plane/propel/icons";
@@ -13,11 +12,14 @@ import { useStickyOperations } from "@/components/stickies/sticky/use-operations
// hooks
import { useSticky } from "@/hooks/use-stickies";
export const WorkspaceStickyHeader = observer(() => {
const { workspaceSlug } = useParams();
type Props = {
workspaceSlug: string;
};
export const WorkspaceStickyHeader = observer(({ workspaceSlug }: Props) => {
// hooks
const { creatingSticky, toggleShowNewSticky } = useSticky();
const { stickyOperations } = useStickyOperations({ workspaceSlug: workspaceSlug?.toString() });
const { stickyOperations } = useStickyOperations({ workspaceSlug });
return (
<>
@@ -1,14 +1,20 @@
"use client";
import { Outlet } from "react-router";
import type { Route } from "../+types/layout";
import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper";
import { WorkspaceStickyHeader } from "./header";
export default function WorkspaceStickiesLayout({ children }: { children: React.ReactNode }) {
export default function WorkspaceStickiesLayout({ params }: Route.ComponentProps) {
const { workspaceSlug } = params;
return (
<>
<AppHeader header={<WorkspaceStickyHeader />} />
<ContentWrapper>{children}</ContentWrapper>
<AppHeader header={<WorkspaceStickyHeader workspaceSlug={workspaceSlug} />} />
<ContentWrapper>
<Outlet />
</ContentWrapper>
</>
);
}
@@ -2,7 +2,7 @@
import { useState } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import type { Route } from "./+types/page";
// plane imports
import { DEFAULT_GLOBAL_VIEWS_LIST } from "@plane/constants";
// components
@@ -11,9 +11,9 @@ import { AllIssueLayoutRoot } from "@/components/issues/issue-layouts/roots/all-
// hooks
import { useWorkspace } from "@/hooks/store/use-workspace";
const GlobalViewIssuesPage = observer(() => {
const GlobalViewIssuesPage: React.FC<Route.ComponentProps> = observer(({ params }) => {
// router
const { globalViewId } = useParams();
const { globalViewId } = params;
// store hooks
const { currentWorkspace } = useWorkspace();
// states
@@ -2,7 +2,6 @@
import { useCallback, useMemo, useState } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// plane imports
import {
EIssueFilterType,
@@ -30,13 +29,16 @@ import { useIssues } from "@/hooks/store/use-issues";
import { useAppRouter } from "@/hooks/use-app-router";
import { GlobalViewLayoutSelection } from "@/plane-web/components/views/helper";
export const GlobalIssuesHeader = observer(() => {
type Props = {
workspaceSlug: string;
globalViewId?: string;
};
export const GlobalIssuesHeader = observer(({ workspaceSlug, globalViewId }: Props) => {
// states
const [createViewModal, setCreateViewModal] = useState(false);
// router
const router = useAppRouter();
const { workspaceSlug, globalViewId: routerGlobalViewId } = useParams();
const globalViewId = routerGlobalViewId ? routerGlobalViewId.toString() : undefined;
// store hooks
const {
issuesFilter: { filters, updateFilters },
@@ -44,43 +46,31 @@ export const GlobalIssuesHeader = observer(() => {
const { getViewDetailsById, currentWorkspaceViews } = useGlobalView();
const { t } = useTranslation();
const issueFilters = globalViewId ? filters[globalViewId.toString()] : undefined;
const issueFilters = globalViewId ? filters[globalViewId] : undefined;
const activeLayout = issueFilters?.displayFilters?.layout;
const viewDetails = globalViewId ? getViewDetailsById(globalViewId) : undefined;
const handleDisplayFilters = useCallback(
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
if (!workspaceSlug || !globalViewId) return;
updateFilters(
workspaceSlug.toString(),
undefined,
EIssueFilterType.DISPLAY_FILTERS,
updatedDisplayFilter,
globalViewId
);
if (!globalViewId) return;
updateFilters(workspaceSlug, undefined, EIssueFilterType.DISPLAY_FILTERS, updatedDisplayFilter, globalViewId);
},
[workspaceSlug, updateFilters, globalViewId]
);
const handleDisplayProperties = useCallback(
(property: Partial<IIssueDisplayProperties>) => {
if (!workspaceSlug || !globalViewId) return;
updateFilters(workspaceSlug.toString(), undefined, EIssueFilterType.DISPLAY_PROPERTIES, property, globalViewId);
if (!globalViewId) return;
updateFilters(workspaceSlug, undefined, EIssueFilterType.DISPLAY_PROPERTIES, property, globalViewId);
},
[workspaceSlug, updateFilters, globalViewId]
);
const handleLayoutChange = useCallback(
(layout: EIssueLayoutTypes) => {
if (!workspaceSlug || !globalViewId) return;
updateFilters(
workspaceSlug.toString(),
undefined,
EIssueFilterType.DISPLAY_FILTERS,
{ layout: layout },
globalViewId
);
if (!globalViewId) return;
updateFilters(workspaceSlug, undefined, EIssueFilterType.DISPLAY_FILTERS, { layout: layout }, globalViewId);
},
[workspaceSlug, updateFilters, globalViewId]
);
@@ -1,14 +1,20 @@
"use client";
import { Outlet } from "react-router";
import type { Route } from "../+types/layout";
import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper";
import { GlobalIssuesHeader } from "./header";
export default function GlobalIssuesLayout({ children }: { children: React.ReactNode }) {
export default function GlobalIssuesLayout({ params }: Route.ComponentProps) {
const { workspaceSlug, globalViewId } = params;
return (
<>
<AppHeader header={<GlobalIssuesHeader />} />
<ContentWrapper>{children}</ContentWrapper>
<AppHeader header={<GlobalIssuesHeader workspaceSlug={workspaceSlug} globalViewId={globalViewId} />} />
<ContentWrapper>
<Outlet />
</ContentWrapper>
</>
);
}
@@ -1,23 +1,28 @@
"use client";
import { Outlet } from "react-router";
import type { Route } from "../+types/layout";
import { CommandPalette } from "@/components/command-palette";
import { ContentWrapper } from "@/components/core/content-wrapper";
import { SettingsHeader } from "@/components/settings/header";
import { AuthenticationWrapper } from "@/lib/wrappers/authentication-wrapper";
import { WorkspaceAuthWrapper } from "@/plane-web/layouts/workspace-wrapper";
export default function SettingsLayout({ children }: { children: React.ReactNode }) {
export default function SettingsLayout({ params }: Route.ComponentProps) {
const { workspaceSlug } = params;
return (
<AuthenticationWrapper>
<WorkspaceAuthWrapper>
<CommandPalette />
<CommandPalette workspaceSlug={workspaceSlug} />
<div className="relative flex h-full w-full overflow-hidden rounded-lg border border-custom-border-200">
<main className="relative flex h-full w-full flex-col overflow-hidden bg-custom-background-100">
{/* Header */}
<SettingsHeader />
{/* Content */}
<ContentWrapper className="p-page-x md:flex w-full">
<div className="w-full h-full overflow-hidden">{children}</div>
<div className="w-full h-full overflow-hidden">
<Outlet />
</div>
</ContentWrapper>
</main>
</div>
@@ -1,6 +1,6 @@
"use client";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import type { Route } from "./+types/page";
import useSWR from "swr";
// components
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
@@ -20,9 +20,9 @@ import { IntegrationService } from "@/services/integrations";
const integrationService = new IntegrationService();
const WorkspaceIntegrationsPage = observer(() => {
const WorkspaceIntegrationsPage: React.FC<Route.ComponentProps> = observer(({ params }) => {
// router
const { workspaceSlug } = useParams();
const { workspaceSlug } = params;
// store hooks
const { currentWorkspace } = useWorkspace();
const { allowPermissions } = useUserPermissions();
@@ -3,6 +3,7 @@
import type { FC, ReactNode } from "react";
import { observer } from "mobx-react";
import { usePathname } from "next/navigation";
import type { Route } from "./+types/layout";
// constants
import { WORKSPACE_SETTINGS_ACCESS } from "@plane/constants";
import type { EUserWorkspaceRoles } from "@plane/types";
@@ -15,19 +16,17 @@ import { useUserPermissions } from "@/hooks/store/user";
// local components
import { WorkspaceSettingsSidebar } from "./sidebar";
export interface IWorkspaceSettingLayout {
children: ReactNode;
}
type Props = Route.ComponentProps & { children: ReactNode };
const WorkspaceSettingLayout: FC<IWorkspaceSettingLayout> = observer((props) => {
const { children } = props;
const WorkspaceSettingLayout: FC<Props> = observer(({ children, params }) => {
const { workspaceSlug } = params;
// store hooks
const { workspaceUserInfo, getWorkspaceRoleByWorkspaceSlug } = useUserPermissions();
// next hooks
const pathname = usePathname();
// derived values
const { workspaceSlug, accessKey } = pathnameToAccessKey(pathname);
const userWorkspaceRole = getWorkspaceRoleByWorkspaceSlug(workspaceSlug.toString());
const { accessKey } = pathnameToAccessKey(pathname);
const userWorkspaceRole = getWorkspaceRoleByWorkspaceSlug(workspaceSlug);
let isAuthorized: boolean | string = false;
if (pathname && workspaceSlug && userWorkspaceRole) {
@@ -37,7 +36,7 @@ const WorkspaceSettingLayout: FC<IWorkspaceSettingLayout> = observer((props) =>
return (
<>
<SettingsMobileNav
hamburgerContent={WorkspaceSettingsSidebar}
hamburgerContent={(props) => <WorkspaceSettingsSidebar {...props} workspaceSlug={workspaceSlug} />}
activePath={getWorkspaceActivePath(pathname) || ""}
/>
<div className="inset-y-0 flex flex-row w-full h-full">
@@ -45,7 +44,7 @@ const WorkspaceSettingLayout: FC<IWorkspaceSettingLayout> = observer((props) =>
<NotAuthorizedView section="settings" className="h-auto" />
) : (
<div className="relative flex h-full w-full">
<div className="hidden md:block">{<WorkspaceSettingsSidebar />}</div>
<div className="hidden md:block">{<WorkspaceSettingsSidebar workspaceSlug={workspaceSlug} />}</div>
<div className="w-full h-full overflow-y-scroll md:pt-page-y">{children}</div>
</div>
)}
@@ -2,8 +2,8 @@
import { useState } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import { Search } from "lucide-react";
import type { Route } from "./+types/page";
// types
import {
EUserPermissions,
@@ -33,12 +33,11 @@ import { useUserPermissions } from "@/hooks/store/user";
import { BillingActionsButton } from "@/plane-web/components/workspace/billing/billing-actions-button";
import { SendWorkspaceInvitationModal } from "@/plane-web/components/workspace/members/invite-modal";
const WorkspaceMembersSettingsPage = observer(() => {
const WorkspaceMembersSettingsPage = observer(({ params }: Route.ComponentProps) => {
// states
const [inviteModal, setInviteModal] = useState(false);
const [searchQuery, setSearchQuery] = useState<string>("");
// router
const { workspaceSlug } = useParams();
const { workspaceSlug } = params;
// store hooks
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
const {
@@ -116,6 +115,7 @@ const WorkspaceMembersSettingsPage = observer(() => {
isOpen={inviteModal}
onClose={() => setInviteModal(false)}
onSubmit={handleWorkspaceInvite}
workspaceSlug={workspaceSlug}
/>
<section
className={cn("w-full h-full", {
@@ -1,5 +1,5 @@
import { observer } from "mobx-react";
import { useParams, usePathname } from "next/navigation";
import { usePathname } from "next/navigation";
import { WORKSPACE_SETTINGS_LINKS, EUserPermissionsLevel } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
// hooks
@@ -8,9 +8,12 @@ import { useAppRouter } from "@/hooks/use-app-router";
// plane web helpers
import { shouldRenderSettingLink } from "@/plane-web/helpers/workspace.helper";
export const MobileWorkspaceSettingsTabs = observer(() => {
type Props = {
workspaceSlug: string;
};
export const MobileWorkspaceSettingsTabs = observer(({ workspaceSlug }: Props) => {
const router = useAppRouter();
const { workspaceSlug } = useParams();
const pathname = usePathname();
const { t } = useTranslation();
// mobx store
@@ -20,8 +23,8 @@ export const MobileWorkspaceSettingsTabs = observer(() => {
<div className="flex-shrink-0 md:hidden sticky inset-0 flex overflow-x-auto bg-custom-background-100 z-10">
{WORKSPACE_SETTINGS_LINKS.map(
(item, index) =>
shouldRenderSettingLink(workspaceSlug.toString(), item.key) &&
allowPermissions(item.access, EUserPermissionsLevel.WORKSPACE, workspaceSlug.toString()) && (
shouldRenderSettingLink(workspaceSlug, item.key) &&
allowPermissions(item.access, EUserPermissionsLevel.WORKSPACE, workspaceSlug) && (
<div
className={`${
item.highlight(pathname, `/${workspaceSlug}`)
@@ -37,13 +37,14 @@ export const WorkspaceActionIcons = ({
type TWorkspaceSettingsSidebarProps = {
isMobile?: boolean;
workspaceSlug: string;
};
export const WorkspaceSettingsSidebar = (props: TWorkspaceSettingsSidebarProps) => {
const { isMobile = false } = props;
const { isMobile = false, workspaceSlug } = props;
// router
const pathname = usePathname();
const { workspaceSlug } = useParams(); // store hooks
// store hooks
const { allowPermissions } = useUserPermissions();
const isAdmin = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.WORKSPACE);
@@ -55,7 +56,7 @@ export const WorkspaceSettingsSidebar = (props: TWorkspaceSettingsSidebarProps)
isAdmin || ![WORKSPACE_SETTINGS_CATEGORY.FEATURES, WORKSPACE_SETTINGS_CATEGORY.DEVELOPER].includes(category)
)}
groupedSettings={GROUPED_WORKSPACE_SETTINGS}
workspaceSlug={workspaceSlug.toString()}
workspaceSlug={workspaceSlug}
isActive={(data: { href: string }) =>
data.href === "/settings"
? pathname === `/${workspaceSlug}${data.href}/`
@@ -63,8 +64,8 @@ export const WorkspaceSettingsSidebar = (props: TWorkspaceSettingsSidebarProps)
}
shouldRender={(data: { key: string; access?: EUserWorkspaceRoles[] | undefined }) =>
data.access
? shouldRenderSettingLink(workspaceSlug.toString(), data.key) &&
allowPermissions(data.access, EUserPermissionsLevel.WORKSPACE, workspaceSlug.toString())
? shouldRenderSettingLink(workspaceSlug, data.key) &&
allowPermissions(data.access, EUserPermissionsLevel.WORKSPACE, workspaceSlug)
: false
}
actionIcons={WorkspaceActionIcons}
@@ -2,7 +2,7 @@
import { useState } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import type { Route } from "./+types/page";
import useSWR from "swr";
import { EUserPermissions, EUserPermissionsLevel, WORKSPACE_SETTINGS_TRACKER_EVENTS } from "@plane/constants";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
@@ -19,11 +19,11 @@ import { useWebhook } from "@/hooks/store/use-webhook";
import { useWorkspace } from "@/hooks/store/use-workspace";
import { useUserPermissions } from "@/hooks/store/user";
const WebhookDetailsPage = observer(() => {
const WebhookDetailsPage: React.FC<Route.ComponentProps> = observer(({ params }) => {
// states
const [deleteWebhookModal, setDeleteWebhookModal] = useState(false);
// router
const { workspaceSlug, webhookId } = useParams();
const { workspaceSlug, webhookId } = params;
// mobx store
const { currentWebhook, fetchWebhookById, updateWebhook } = useWebhook();
const { currentWorkspace } = useWorkspace();
@@ -2,7 +2,7 @@
import React, { useEffect, useState } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import type { Route } from "./+types/page";
import useSWR from "swr";
// plane imports
import { EUserPermissions, EUserPermissionsLevel, WORKSPACE_SETTINGS_TRACKER_ELEMENTS } from "@plane/constants";
@@ -22,11 +22,11 @@ import { useWorkspace } from "@/hooks/store/use-workspace";
import { useUserPermissions } from "@/hooks/store/user";
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
const WebhooksListPage = observer(() => {
const WebhooksListPage: React.FC<Route.ComponentProps> = observer(({ params }) => {
// states
const [showCreateWebhookModal, setShowCreateWebhookModal] = useState(false);
// router
const { workspaceSlug } = useParams();
const { workspaceSlug } = params;
// plane hooks
const { t } = useTranslation();
// mobx store
@@ -2,7 +2,7 @@
import React from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import type { Route } from "./+types/page";
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
@@ -20,11 +20,9 @@ import { useUserPermissions } from "@/hooks/store/user";
// plane web imports
import { CustomAutomationsRoot } from "@/plane-web/components/automations/root";
const AutomationSettingsPage = observer(() => {
// router
const { workspaceSlug: workspaceSlugParam, projectId: projectIdParam } = useParams();
const workspaceSlug = workspaceSlugParam?.toString();
const projectId = projectIdParam?.toString();
const AutomationSettingsPage: React.FC<Route.ComponentProps> = observer(({ params }) => {
// router params
const { workspaceSlug, projectId } = params;
// store hooks
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
const { currentProjectDetails: projectDetails, updateProject } = useProject();
@@ -64,7 +62,7 @@ const AutomationSettingsPage = observer(() => {
<AutoArchiveAutomation handleChange={handleChange} />
<AutoCloseAutomation handleChange={handleChange} />
</section>
<CustomAutomationsRoot projectId={projectId} workspaceSlug={workspaceSlug} />
{projectId && workspaceSlug && <CustomAutomationsRoot projectId={projectId} workspaceSlug={workspaceSlug} />}
</SettingsContentWrapper>
);
});
@@ -1,7 +1,7 @@
"use client";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import type { Route } from "./+types/page";
// plane imports
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
@@ -19,9 +19,9 @@ import { useUserPermissions } from "@/hooks/store/user";
import { ProjectTeamspaceList } from "@/plane-web/components/projects/teamspaces/teamspace-list";
import { getProjectSettingsPageLabelI18nKey } from "@/plane-web/helpers/project-settings";
const MembersSettingsPage = observer(() => {
// router
const { workspaceSlug: routerWorkspaceSlug, projectId: routerProjectId } = useParams();
const MembersSettingsPage: React.FC<Route.ComponentProps> = observer(({ params }) => {
// router params
const { workspaceSlug: routerWorkspaceSlug, projectId: routerProjectId } = params;
// plane hooks
const { t } = useTranslation();
// store hooks
@@ -46,9 +46,13 @@ const MembersSettingsPage = observer(() => {
<SettingsContentWrapper size="lg">
<PageHead title={pageTitle} />
<SettingsHeading title={t(getProjectSettingsPageLabelI18nKey("members", "common.members"))} />
<ProjectSettingsMemberDefaults projectId={projectId} workspaceSlug={workspaceSlug} />
<ProjectTeamspaceList projectId={projectId} workspaceSlug={workspaceSlug} />
<ProjectMemberList projectId={projectId} workspaceSlug={workspaceSlug} />
{projectId && workspaceSlug && (
<>
<ProjectSettingsMemberDefaults projectId={projectId} workspaceSlug={workspaceSlug} />
<ProjectTeamspaceList projectId={projectId} workspaceSlug={workspaceSlug} />
<ProjectMemberList projectId={projectId} workspaceSlug={workspaceSlug} />
</>
)}
</SettingsContentWrapper>
);
});
@@ -2,7 +2,7 @@
import { useState } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import type { Route } from "./+types/page";
import useSWR from "swr";
// plane imports
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
@@ -19,12 +19,12 @@ import { SettingsContentWrapper } from "@/components/settings/content-wrapper";
import { useProject } from "@/hooks/store/use-project";
import { useUserPermissions } from "@/hooks/store/user";
const ProjectSettingsPage = observer(() => {
const ProjectSettingsPage: React.FC<Route.ComponentProps> = observer(({ params }) => {
// states
const [selectProject, setSelectedProject] = useState<string | null>(null);
const [archiveProject, setArchiveProject] = useState<boolean>(false);
// router
const { workspaceSlug, projectId } = useParams();
// router params
const { workspaceSlug, projectId } = params;
// store hooks
const { currentProjectDetails, fetchProjectDetails } = useProject();
const { allowPermissions } = useUserPermissions();
@@ -33,15 +33,10 @@ const ProjectSettingsPage = observer(() => {
// TODO: removed this API if not necessary
const { isLoading } = useSWR(
workspaceSlug && projectId ? `PROJECT_DETAILS_${projectId}` : null,
workspaceSlug && projectId ? () => fetchProjectDetails(workspaceSlug.toString(), projectId.toString()) : null
workspaceSlug && projectId ? () => fetchProjectDetails(workspaceSlug, projectId) : null
);
// derived values
const isAdmin = allowPermissions(
[EUserPermissions.ADMIN],
EUserPermissionsLevel.PROJECT,
workspaceSlug.toString(),
projectId.toString()
);
const isAdmin = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT, workspaceSlug, projectId);
const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - General Settings` : undefined;
@@ -51,8 +46,8 @@ const ProjectSettingsPage = observer(() => {
{currentProjectDetails && workspaceSlug && projectId && (
<>
<ArchiveRestoreProjectModal
workspaceSlug={workspaceSlug.toString()}
projectId={projectId.toString()}
workspaceSlug={workspaceSlug}
projectId={projectId}
isOpen={archiveProject}
onClose={() => setArchiveProject(false)}
archive
@@ -69,8 +64,8 @@ const ProjectSettingsPage = observer(() => {
{currentProjectDetails && workspaceSlug && projectId && !isLoading ? (
<ProjectDetailsForm
project={currentProjectDetails}
workspaceSlug={workspaceSlug.toString()}
projectId={projectId.toString()}
workspaceSlug={workspaceSlug}
projectId={projectId}
isAdmin={isAdmin}
/>
) : (
@@ -1,9 +1,10 @@
"use client";
import type { ReactNode } from "react";
import { useEffect } from "react";
import { observer } from "mobx-react";
import { useParams, usePathname } from "next/navigation";
import { usePathname } from "next/navigation";
import type { Route } from "./+types/layout";
import type { PropsWithChildren } from "react";
// components
import { getProjectActivePath } from "@/components/settings/helper";
import { SettingsMobileNav } from "@/components/settings/mobile";
@@ -12,16 +13,11 @@ import { useProject } from "@/hooks/store/use-project";
import { useAppRouter } from "@/hooks/use-app-router";
import { ProjectAuthWrapper } from "@/plane-web/layouts/project-wrapper";
type Props = {
children: ReactNode;
};
const ProjectSettingsLayout = observer((props: Props) => {
const { children } = props;
const ProjectSettingsLayout: React.FC<PropsWithChildren<Route.ComponentProps>> = observer(({ children, params }) => {
// router
const router = useAppRouter();
const pathname = usePathname();
const { workspaceSlug, projectId } = useParams();
const { workspaceSlug, projectId } = params;
const { joinedProjectIds } = useProject();
useEffect(() => {
@@ -34,7 +30,7 @@ const ProjectSettingsLayout = observer((props: Props) => {
return (
<>
<SettingsMobileNav hamburgerContent={ProjectSettingsSidebar} activePath={getProjectActivePath(pathname) || ""} />
<ProjectAuthWrapper workspaceSlug={workspaceSlug?.toString()} projectId={projectId?.toString()}>
<ProjectAuthWrapper workspaceSlug={workspaceSlug!} projectId={projectId!}>
<div className="relative flex h-full w-full">
<div className="hidden md:block">{projectId && <ProjectSettingsSidebar />}</div>
<div className="w-full h-full overflow-y-scroll md:pt-page-y">{children}</div>
@@ -1,12 +1,16 @@
"use client";
import { Outlet } from "react-router";
import type { Route } from "../+types/layout";
import { AppRailProvider } from "@/hooks/context/app-rail-context";
import { WorkspaceContentWrapper } from "@/plane-web/components/workspace/content-wrapper";
export default function WorkspaceLayout({ children }: { children: React.ReactNode }) {
export default function WorkspaceLayout() {
return (
<AppRailProvider>
<WorkspaceContentWrapper>{children}</WorkspaceContentWrapper>
<WorkspaceContentWrapper>
<Outlet />
</WorkspaceContentWrapper>
</AppRailProvider>
);
}
@@ -1,9 +0,0 @@
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Forgot Password - Plane",
};
export default function ForgotPasswordLayout({ children }: { children: React.ReactNode }) {
return children;
}
@@ -1,5 +1,9 @@
"use client";
import type { Route } from "./+types/page";
export const meta: Route.MetaFunction = () => [{ title: "Forgot Password - Plane" }];
import { observer } from "mobx-react";
// components
import { ForgotPasswordForm } from "@/components/account/auth-forms/forgot-password";
@@ -1,9 +0,0 @@
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Reset Password - Plane",
};
export default function ResetPasswordLayout({ children }: { children: React.ReactNode }) {
return children;
}
@@ -1,5 +1,9 @@
"use client";
import type { Route } from "./+types/page";
export const meta: Route.MetaFunction = () => [{ title: "Reset Password - Plane" }];
// plane imports
import { EAuthModes } from "@plane/constants";
// components
@@ -1,9 +0,0 @@
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Set Password - Plane",
};
export default function SetPasswordLayout({ children }: { children: React.ReactNode }) {
return children;
}
@@ -1,5 +1,9 @@
"use client";
import type { Route } from "./+types/page";
export const meta: Route.MetaFunction = () => [{ title: "Set Password - Plane" }];
// plane imports
import { EAuthModes } from "@plane/constants";
// components
@@ -1,9 +0,0 @@
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Create Workspace",
};
export default function CreateWorkspaceLayout({ children }: { children: React.ReactNode }) {
return children;
}
+5 -1
View File
@@ -1,5 +1,9 @@
"use client";
import type { Route } from "./+types/page";
export const meta: Route.MetaFunction = () => [{ title: "Create Workspace" }];
import { useState } from "react";
import { observer } from "mobx-react";
import Image from "next/image";
@@ -19,7 +23,7 @@ import { AuthenticationWrapper } from "@/lib/wrappers/authentication-wrapper";
// plane web helpers
import { getIsWorkspaceCreationDisabled } from "@/plane-web/helpers/instance.helper";
// images
import WorkspaceCreationDisabled from "@/public/workspace/workspace-creation-disabled.png";
import WorkspaceCreationDisabled from "@/app/assets/workspace/workspace-creation-disabled.png";
const CreateWorkspacePage = observer(() => {
const { t } = useTranslation();
@@ -1,3 +0,0 @@
export default function InstallationProviderLayout({ children }: { children: React.ReactNode }) {
return children;
}
@@ -1,7 +1,8 @@
"use client";
import React, { useEffect } from "react";
import { useParams, useSearchParams } from "next/navigation";
import type { Route } from "./+types/page";
import { useSearchParams } from "next/navigation";
// ui
import { LogoSpinner } from "@/components/common/logo-spinner";
// services
@@ -10,9 +11,9 @@ import { AppInstallationService } from "@/services/app_installation.service";
// services
const appInstallationService = new AppInstallationService();
export default function AppPostInstallation() {
export default function AppPostInstallation({ params }: Route.ComponentProps) {
// params
const { provider } = useParams();
const { provider } = params;
// query params
const searchParams = useSearchParams();
const installation_id = searchParams.get("installation_id");
@@ -1,9 +0,0 @@
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Invitations",
};
export default function InvitationsLayout({ children }: { children: React.ReactNode }) {
return children;
}
+5 -1
View File
@@ -1,5 +1,9 @@
"use client";
import type { Route } from "./+types/page";
export const meta: Route.MetaFunction = () => [{ title: "Invitations" }];
import React, { useState } from "react";
import { observer } from "mobx-react";
import Link from "next/link";
@@ -30,7 +34,7 @@ import { AuthenticationWrapper } from "@/lib/wrappers/authentication-wrapper";
// plane web services
import { WorkspaceService } from "@/plane-web/services";
// images
import emptyInvitation from "@/public/empty-state/invitation.svg";
import emptyInvitation from "@/app/assets/empty-state/invitation.svg";
const workspaceService = new WorkspaceService();
+11 -2
View File
@@ -1,13 +1,22 @@
"use client";
import { useEffect } from "react";
import ReactDOM from "react-dom";
// https://nextjs.org/docs/app/api-reference/functions/generate-metadata#link-relpreload
export const usePreloadResources = () => {
useEffect(() => {
const preloadItem = (url: string) => {
ReactDOM.preload(url, { as: "fetch", crossOrigin: "use-credentials" });
try {
const link = document.createElement("link");
link.rel = "preload";
// as="fetch" isn't fully typed across all TS DOM lib versions, so set via attribute
link.setAttribute("as", "fetch");
link.href = url;
link.crossOrigin = "use-credentials" as any;
document.head.appendChild(link);
} catch (e) {
// no-op fallback
}
};
const urls = [
+5 -21
View File
@@ -1,31 +1,15 @@
import type { Metadata, Viewport } from "next";
import { Outlet } from "react-router";
import type { Route } from "./+types/layout";
import { PreloadResources } from "./layout.preload";
// styles
import "@/styles/command-pallette.css";
import "@/styles/emoji.css";
import "@plane/propel/styles/react-day-picker";
import "@plane/propel/styles/react-day-picker.css";
export const metadata: Metadata = {
robots: {
index: false,
follow: false,
},
};
export const viewport: Viewport = {
minimumScale: 1,
initialScale: 1,
width: "device-width",
viewportFit: "cover",
};
export default function AppLayout({ children }: { children: React.ReactNode }) {
export default function AppLayout() {
return (
<>
<PreloadResources />
{children}
<Outlet />
</>
);
}
-9
View File
@@ -1,9 +0,0 @@
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Onboarding",
};
export default function OnboardingLayout({ children }: { children: React.ReactNode }) {
return children;
}
+6 -7
View File
@@ -8,13 +8,10 @@ import { AuthenticationWrapper } from "@/lib/wrappers/authentication-wrapper";
// layout
import { ProfileLayoutSidebar } from "./sidebar";
type Props = {
children: ReactNode;
};
export default function ProfileSettingsLayout(props: Props) {
const { children } = props;
import { Outlet } from "react-router";
import type { Route } from "../+types/layout";
export default function ProfileSettingsLayout() {
return (
<>
<CommandPalette />
@@ -22,7 +19,9 @@ export default function ProfileSettingsLayout(props: Props) {
<div className="relative flex h-full w-full overflow-hidden rounded-lg border border-custom-border-200">
<ProfileLayoutSidebar />
<main className="relative flex h-full w-full flex-col overflow-hidden bg-custom-background-100">
<div className="h-full w-full overflow-hidden">{children}</div>
<div className="h-full w-full overflow-hidden">
<Outlet />
</div>
</main>
</div>
</AuthenticationWrapper>
-13
View File
@@ -1,13 +0,0 @@
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Sign up - Plane",
robots: {
index: true,
follow: false,
},
};
export default function SignUpLayout({ children }: { children: React.ReactNode }) {
return children;
}
@@ -1,9 +0,0 @@
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Workspace Invitations",
};
export default function WorkspaceInvitationsLayout({ children }: { children: React.ReactNode }) {
return children;
}
+4 -17
View File
@@ -1,19 +1,6 @@
import type { Metadata, Viewport } from "next";
import { Outlet } from "react-router";
import type { Route } from "./+types/layout";
export const metadata: Metadata = {
robots: {
index: true,
follow: false,
},
};
export const viewport: Viewport = {
minimumScale: 1,
initialScale: 1,
width: "device-width",
viewportFit: "cover",
};
export default function HomeLayout({ children }: { children: React.ReactNode }) {
return <>{children}</>;
export default function HomeLayout() {
return <Outlet />;
}

Some files were not shown because too many files have changed in this diff Show More