[WEB-4338] fix: incorrect error code in project retrieve API (#7234)

* fix: project error message and status code

* fix: incorrect member role check

* fix: project error message and status code

* fix: improve project permission checks and error handling in ProjectViewSet

* feat: enhance project settings layout with better loading strategy and fix all flicker

* fix: prevent rendering during project loading in ProjectAuthWrapper

* refactor: adjust layout structure in ProjectDetailSettingsLayout and enhance access restriction logic in ProjectAccessRestriction

* refactor: replace ProjectAccessRestriction component with updated version and enhance error handling

- Deleted the old ProjectAccessRestriction component.
- Introduced a new ProjectAccessRestriction component with improved error handling and user prompts for joining projects.
- Updated translations for new error states in multiple languages.

* fix: enhance error handling in IssueDetailsPage and remove JoinProject component

---------

Co-authored-by: Dheeraj Kumar Ketireddy <dheeru0198@gmail.com>
Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com>
This commit is contained in:
Sangeetha 2025-12-01 17:14:01 +05:30 committed by GitHub
parent 8db95d9ec0
commit 60220801ac
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
43 changed files with 609 additions and 516 deletions

View file

@ -82,7 +82,7 @@ function IssueDetailsPage({ params }: Route.ComponentProps) {
return (
<>
<PageHead title={pageTitle} />
{error ? (
{error && !issueLoader ? (
<EmptyState
image={resolvedTheme === "dark" ? emptyIssueDark : emptyIssueLight}
title={t("issue.empty_state.issue_detail.title")}

View file

@ -11,6 +11,7 @@ import { AppSidebarToggleButton } from "@/components/sidebar/sidebar-toggle-butt
// hooks
import { useAppTheme } from "@/hooks/store/use-app-theme";
import { useProjectNavigationPreferences } from "@/hooks/use-navigation-preferences";
import { ProjectAuthWrapper } from "@/plane-web/layouts/project-wrapper";
// local imports
import type { Route } from "./+types/layout";
@ -44,7 +45,9 @@ function ProjectLayout({ params }: Route.ComponentProps) {
</Row>
</div>
)}
<Outlet />
<ProjectAuthWrapper workspaceSlug={workspaceSlug} projectId={projectId}>
<Outlet />
</ProjectAuthWrapper>
</>
);
}

View file

@ -1,14 +0,0 @@
import { Outlet } from "react-router";
// plane web layouts
import { ProjectAuthWrapper } from "@/plane-web/layouts/project-wrapper";
import type { Route } from "./+types/layout";
export default function ProjectDetailLayout({ params }: Route.ComponentProps) {
// router
const { workspaceSlug, projectId } = params;
return (
<ProjectAuthWrapper workspaceSlug={workspaceSlug} projectId={projectId}>
<Outlet />
</ProjectAuthWrapper>
);
}

View file

@ -1,7 +1,6 @@
import { observer } from "mobx-react";
import { useTranslation } from "@plane/i18n";
// components
import { LogoSpinner } from "@/components/common/logo-spinner";
import { PageHead } from "@/components/core/page-title";
import { ProfileForm } from "@/components/profile/form";
// hooks
@ -12,13 +11,7 @@ function ProfileSettingsPage() {
// store hooks
const { data: currentUser, userProfile } = useUser();
if (!currentUser)
return (
<div className="grid h-full w-full place-items-center px-4 sm:px-0">
<LogoSpinner />
</div>
);
if (!currentUser) return <></>;
return (
<>
<PageHead title={`${t("profile.label")} - ${t("general_settings")}`} />

View file

@ -2,7 +2,6 @@ import { observer } from "mobx-react";
// plane imports
import { useTranslation } from "@plane/i18n";
// components
import { LogoSpinner } from "@/components/common/logo-spinner";
import { PageHead } from "@/components/core/page-title";
import { PreferencesList } from "@/components/preferences/list";
import { LanguageTimezone } from "@/components/profile/preferences/language-timezone";
@ -16,30 +15,23 @@ function ProfileAppearancePage() {
// hooks
const { data: userProfile } = useUserProfile();
if (!userProfile) return <></>;
return (
<>
<PageHead title={`${t("profile.label")} - ${t("preferences")}`} />
{userProfile ? (
<>
<div className="flex flex-col gap-4 w-full">
<div>
<SettingsHeading
title={t("account_settings.preferences.heading")}
description={t("account_settings.preferences.description")}
/>
<PreferencesList />
</div>
<div>
<ProfileSettingContentHeader title={t("language_and_time")} />
<LanguageTimezone />
</div>
</div>
</>
) : (
<div className="grid h-full w-full place-items-center px-4 sm:px-0">
<LogoSpinner />
<div className="flex flex-col gap-4 w-full">
<div>
<SettingsHeading
title={t("account_settings.preferences.heading")}
description={t("account_settings.preferences.description")}
/>
<PreferencesList />
</div>
)}
<div>
<ProfileSettingContentHeader title={t("language_and_time")} />
<LanguageTimezone />
</div>
</div>
</>
);
}

View file

@ -0,0 +1,33 @@
import { observer } from "mobx-react";
import { usePathname } from "next/navigation";
import { Outlet } from "react-router";
// components
import { getProjectActivePath } from "@/components/settings/helper";
import { SettingsMobileNav } from "@/components/settings/mobile";
import { ProjectSettingsSidebar } from "@/components/settings/project/sidebar";
// plane web imports
import { ProjectAuthWrapper } from "@/plane-web/layouts/project-wrapper";
// types
import type { Route } from "./+types/layout";
function ProjectDetailSettingsLayout({ params }: Route.ComponentProps) {
const { workspaceSlug, projectId } = params;
// router
const pathname = usePathname();
return (
<>
<SettingsMobileNav hamburgerContent={ProjectSettingsSidebar} activePath={getProjectActivePath(pathname) || ""} />
<div className="relative flex h-full w-full">
<div className="hidden md:block">{projectId && <ProjectSettingsSidebar />}</div>
<ProjectAuthWrapper workspaceSlug={workspaceSlug} projectId={projectId}>
<div className="w-full h-full overflow-y-scroll md:pt-page-y">
<Outlet />
</div>
</ProjectAuthWrapper>
</div>
</>
);
}
export default observer(ProjectDetailSettingsLayout);

View file

@ -1,6 +1,5 @@
import { useState } from "react";
import { observer } from "mobx-react";
import useSWR from "swr";
// plane imports
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
// components
@ -24,12 +23,8 @@ function ProjectSettingsPage({ params }: Route.ComponentProps) {
// router
const { workspaceSlug, projectId } = params;
// store hooks
const { currentProjectDetails, fetchProjectDetails } = useProject();
const { currentProjectDetails } = useProject();
const { allowPermissions } = useUserPermissions();
// api call to fetch project details
// TODO: removed this API if not necessary
const { isLoading } = useSWR(`PROJECT_DETAILS_${projectId}`, () => fetchProjectDetails(workspaceSlug, projectId));
// derived values
const isAdmin = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT, workspaceSlug, projectId);
@ -56,7 +51,7 @@ function ProjectSettingsPage({ params }: Route.ComponentProps) {
)}
<div className={`w-full ${isAdmin ? "" : "opacity-60"}`}>
{currentProjectDetails && !isLoading ? (
{currentProjectDetails ? (
<ProjectDetailsForm
project={currentProjectDetails}
workspaceSlug={workspaceSlug}

View file

@ -1,21 +1,17 @@
import { useEffect } from "react";
import { observer } from "mobx-react";
import { usePathname } from "next/navigation";
import { Outlet } from "react-router";
// components
import { getProjectActivePath } from "@/components/settings/helper";
import { SettingsMobileNav } from "@/components/settings/mobile";
import { ProjectSettingsSidebar } from "@/components/settings/project/sidebar";
// hooks
import { useProject } from "@/hooks/store/use-project";
import { useAppRouter } from "@/hooks/use-app-router";
import { ProjectAuthWrapper } from "@/plane-web/layouts/project-wrapper";
// types
import type { Route } from "./+types/layout";
function ProjectSettingsLayout({ params }: Route.ComponentProps) {
const { workspaceSlug, projectId } = params;
// router
const router = useAppRouter();
const pathname = usePathname();
const { workspaceSlug, projectId } = params;
// store hooks
const { joinedProjectIds } = useProject();
useEffect(() => {
@ -25,19 +21,7 @@ function ProjectSettingsLayout({ params }: Route.ComponentProps) {
}
}, [joinedProjectIds, router, workspaceSlug, projectId]);
return (
<>
<SettingsMobileNav hamburgerContent={ProjectSettingsSidebar} activePath={getProjectActivePath(pathname) || ""} />
<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">
<Outlet />
</div>
</div>
</ProjectAuthWrapper>
</>
);
return <Outlet />;
}
export default observer(ProjectSettingsLayout);

View file

@ -1,8 +1,14 @@
import { observer } from "mobx-react";
import Link from "next/link";
import { useTheme } from "next-themes";
// plane imports
import { PROJECT_TRACKER_ELEMENTS } from "@plane/constants";
import { Button, getButtonStyling } from "@plane/propel/button";
import { cn } from "@plane/utils";
// assets
import ProjectDarkEmptyState from "@/app/assets/empty-state/project-settings/no-projects-dark.png?url";
import ProjectLightEmptyState from "@/app/assets/empty-state/project-settings/no-projects-light.png?url";
// hooks
import { useCommandPalette } from "@/hooks/store/use-command-palette";
function ProjectSettingsPage() {
@ -10,13 +16,10 @@ function ProjectSettingsPage() {
const { resolvedTheme } = useTheme();
const { toggleCreateProjectModal } = useCommandPalette();
// derived values
const resolvedPath =
resolvedTheme === "dark"
? "/empty-state/project-settings/no-projects-dark.png"
: "/empty-state/project-settings/no-projects-light.png";
const resolvedPath = resolvedTheme === "dark" ? ProjectDarkEmptyState : ProjectLightEmptyState;
return (
<div className="flex flex-col gap-4 items-center justify-center h-full max-w-[480px] mx-auto">
<img src={resolvedPath} className="w-full h-full object-contain" alt="No projects yet" />
<img src={resolvedPath} alt="No projects yet" />
<div className="text-lg font-semibold text-custom-text-350">No projects yet</div>
<div className="text-sm text-custom-text-350 text-center">
Projects act as the foundation for goal-driven work. They let you manage your teams, tasks, and everything you
@ -38,4 +41,4 @@ function ProjectSettingsPage() {
);
}
export default ProjectSettingsPage;
export default observer(ProjectSettingsPage);