diff --git a/apps/api/plane/app/views/project/base.py b/apps/api/plane/app/views/project/base.py index 3aa356491..24f2f1f4a 100644 --- a/apps/api/plane/app/views/project/base.py +++ b/apps/api/plane/app/views/project/base.py @@ -1,43 +1,43 @@ # Python imports -import boto3 -from django.conf import settings -from django.utils import timezone import json +import boto3 + # Django imports -from django.db.models import Exists, F, OuterRef, Prefetch, Q, Subquery +from django.conf import settings from django.core.serializers.json import DjangoJSONEncoder +from django.db.models import Exists, F, OuterRef, Prefetch, Q, Subquery +from django.utils import timezone # Third Party imports -from rest_framework.response import Response from rest_framework import status from rest_framework.permissions import AllowAny +from rest_framework.response import Response # Module imports -from plane.app.views.base import BaseViewSet, BaseAPIView +from plane.app.permissions import ROLE, ProjectMemberPermission, allow_permission from plane.app.serializers import ( - ProjectSerializer, - ProjectListSerializer, DeployBoardSerializer, + ProjectListSerializer, + ProjectSerializer, ) - -from plane.app.permissions import ProjectMemberPermission, allow_permission, ROLE +from plane.app.views.base import BaseAPIView, BaseViewSet +from plane.bgtasks.recent_visited_task import recent_visited_task +from plane.bgtasks.webhook_task import model_activity, webhook_activity from plane.db.models import ( - UserFavorite, - Intake, DeployBoard, + Intake, IssueUserProperty, Project, ProjectIdentifier, ProjectMember, + ProjectNetwork, State, DEFAULT_STATES, Workspace, WorkspaceMember, ) from plane.utils.cache import cache_response -from plane.bgtasks.webhook_task import model_activity, webhook_activity -from plane.bgtasks.recent_visited_task import recent_visited_task from plane.utils.exception_logger import log_exception from plane.utils.host import base_host @@ -210,19 +210,25 @@ class ProjectViewSet(BaseViewSet): @allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE") def retrieve(self, request, slug, pk): - project = ( - self.get_queryset() - .filter( - project_projectmember__member=self.request.user, - project_projectmember__is_active=True, - ) - .filter(archived_at__isnull=True) - .filter(pk=pk) - ).first() + project = self.get_queryset().filter(archived_at__isnull=True).filter(pk=pk).first() if project is None: return Response({"error": "Project does not exist"}, status=status.HTTP_404_NOT_FOUND) + member_ids = [str(project_member.member_id) for project_member in project.members_list] + + if str(request.user.id) not in member_ids: + if project.network == ProjectNetwork.SECRET.value: + return Response( + {"error": "You do not have permission"}, + status=status.HTTP_403_FORBIDDEN, + ) + else: + return Response( + {"error": "You are not a member of this project"}, + status=status.HTTP_409_CONFLICT, + ) + recent_visited_task.delay( slug=slug, project_id=pk, diff --git a/apps/api/plane/db/models/__init__.py b/apps/api/plane/db/models/__init__.py index d24a14564..cf58632ac 100644 --- a/apps/api/plane/db/models/__init__.py +++ b/apps/api/plane/db/models/__init__.py @@ -52,6 +52,7 @@ from .project import ( ProjectIdentifier, ProjectMember, ProjectMemberInvite, + ProjectNetwork, ProjectPublicMember, ) from .session import Session diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/browse/[workItem]/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/browse/[workItem]/page.tsx index 22a7c82d2..1aba8eae8 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/browse/[workItem]/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/browse/[workItem]/page.tsx @@ -82,7 +82,7 @@ function IssueDetailsPage({ params }: Route.ComponentProps) { return ( <> - {error ? ( + {error && !issueLoader ? ( )} - + + + ); } diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/layout.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/layout.tsx deleted file mode 100644 index 38d0ac946..000000000 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/layout.tsx +++ /dev/null @@ -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 ( - - - - ); -} diff --git a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/page.tsx index 7c6d22c99..7e5268677 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/page.tsx @@ -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 ( -
- -
- ); - + if (!currentUser) return <>; return ( <> diff --git a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/preferences/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/preferences/page.tsx index 397b46603..1df1df643 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/preferences/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/preferences/page.tsx @@ -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 ( <> - {userProfile ? ( - <> -
-
- - -
-
- - -
-
- - ) : ( -
- +
+
+ +
- )} +
+ + +
+
); } diff --git a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/layout.tsx b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/layout.tsx new file mode 100644 index 000000000..8e8c09064 --- /dev/null +++ b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/layout.tsx @@ -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 ( + <> + +
+
{projectId && }
+ +
+ +
+
+
+ + ); +} + +export default observer(ProjectDetailSettingsLayout); diff --git a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/page.tsx index 667a33cb0..ea0486fa3 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/page.tsx @@ -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) { )}
- {currentProjectDetails && !isLoading ? ( + {currentProjectDetails ? ( { @@ -25,19 +21,7 @@ function ProjectSettingsLayout({ params }: Route.ComponentProps) { } }, [joinedProjectIds, router, workspaceSlug, projectId]); - return ( - <> - - -
-
{projectId && }
-
- -
-
-
- - ); + return ; } export default observer(ProjectSettingsLayout); diff --git a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/page.tsx index 79f6597e7..7bd356877 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/page.tsx @@ -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 (
- No projects yet + No projects yet
No projects yet
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); diff --git a/apps/web/app/routes/core.ts b/apps/web/app/routes/core.ts index 17d1fa7bb..ccb9d78d3 100644 --- a/apps/web/app/routes/core.ts +++ b/apps/web/app/routes/core.ts @@ -108,9 +108,13 @@ export const coreRoutes: RouteConfigEntry[] = [ ), ]), - // ==================================================================== - // PROJECT LEVEL ROUTES - // ==================================================================== + // Archived Projects + layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/archives/layout.tsx", [ + route( + ":workspaceSlug/projects/archives", + "./(all)/[workspaceSlug]/(projects)/projects/(detail)/archives/page.tsx" + ), + ]), // -------------------------------------------------------------------- // PROJECT LEVEL ROUTES @@ -122,136 +126,123 @@ export const coreRoutes: RouteConfigEntry[] = [ ]), // Project Detail - layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/layout.tsx", [ - layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/layout.tsx", [ - // Project Issues List - layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/layout.tsx", [ - route( - ":workspaceSlug/projects/:projectId/issues", - "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/page.tsx" - ), - ]), - // Issue Detail + layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/layout.tsx", [ + // Project Issues List + layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/layout.tsx", [ route( - ":workspaceSlug/projects/:projectId/issues/:issueId", - "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(detail)/[issueId]/page.tsx" - ), - - // Cycle Detail - layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/layout.tsx", [ - route( - ":workspaceSlug/projects/:projectId/cycles/:cycleId", - "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/[cycleId]/page.tsx" - ), - ]), - - // Cycles List - layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/layout.tsx", [ - route( - ":workspaceSlug/projects/:projectId/cycles", - "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/page.tsx" - ), - ]), - - // Module Detail - layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/layout.tsx", [ - route( - ":workspaceSlug/projects/:projectId/modules/:moduleId", - "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/[moduleId]/page.tsx" - ), - ]), - - // Modules List - layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/layout.tsx", [ - route( - ":workspaceSlug/projects/:projectId/modules", - "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/page.tsx" - ), - ]), - - // View Detail - layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/layout.tsx", [ - route( - ":workspaceSlug/projects/:projectId/views/:viewId", - "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/[viewId]/page.tsx" - ), - ]), - - // Views List - layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/layout.tsx", [ - route( - ":workspaceSlug/projects/:projectId/views", - "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/page.tsx" - ), - ]), - - // Page Detail - layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(detail)/layout.tsx", [ - route( - ":workspaceSlug/projects/:projectId/pages/:pageId", - "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(detail)/[pageId]/page.tsx" - ), - ]), - - // Pages List - layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/layout.tsx", [ - route( - ":workspaceSlug/projects/:projectId/pages", - "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/page.tsx" - ), - ]), - // Intake list - layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/intake/layout.tsx", [ - route( - ":workspaceSlug/projects/:projectId/intake", - "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/intake/page.tsx" - ), - ]), - ]), - - // Archived Projects - layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/archives/layout.tsx", [ - route( - ":workspaceSlug/projects/archives", - "./(all)/[workspaceSlug]/(projects)/projects/(detail)/archives/page.tsx" + ":workspaceSlug/projects/:projectId/issues", + "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/page.tsx" ), ]), - - // Project Archives - Issues, Cycles, Modules - // Project Archives - Issues - List - layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(list)/layout.tsx", [ - route( - ":workspaceSlug/projects/:projectId/archives/issues", - "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(list)/page.tsx" - ), - ]), - - // Project Archives - Issues - Detail - layout( - "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/layout.tsx", - [ - route( - ":workspaceSlug/projects/:projectId/archives/issues/:archivedIssueId", - "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/[archivedIssueId]/page.tsx" - ), - ] + // Issue Detail + route( + ":workspaceSlug/projects/:projectId/issues/:issueId", + "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(detail)/[issueId]/page.tsx" ), - // Project Archives - Cycles - layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/cycles/layout.tsx", [ + // Cycle Detail + layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/layout.tsx", [ route( - ":workspaceSlug/projects/:projectId/archives/cycles", - "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/cycles/page.tsx" + ":workspaceSlug/projects/:projectId/cycles/:cycleId", + "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/[cycleId]/page.tsx" ), ]), - // Project Archives - Modules - layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/modules/layout.tsx", [ + // Cycles List + layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/layout.tsx", [ route( - ":workspaceSlug/projects/:projectId/archives/modules", - "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/modules/page.tsx" + ":workspaceSlug/projects/:projectId/cycles", + "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/page.tsx" ), ]), + + // Module Detail + layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/layout.tsx", [ + route( + ":workspaceSlug/projects/:projectId/modules/:moduleId", + "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/[moduleId]/page.tsx" + ), + ]), + + // Modules List + layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/layout.tsx", [ + route( + ":workspaceSlug/projects/:projectId/modules", + "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/page.tsx" + ), + ]), + + // View Detail + layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/layout.tsx", [ + route( + ":workspaceSlug/projects/:projectId/views/:viewId", + "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/[viewId]/page.tsx" + ), + ]), + + // Views List + layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/layout.tsx", [ + route( + ":workspaceSlug/projects/:projectId/views", + "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/page.tsx" + ), + ]), + + // Page Detail + layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(detail)/layout.tsx", [ + route( + ":workspaceSlug/projects/:projectId/pages/:pageId", + "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(detail)/[pageId]/page.tsx" + ), + ]), + + // Pages List + layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/layout.tsx", [ + route( + ":workspaceSlug/projects/:projectId/pages", + "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/page.tsx" + ), + ]), + // Intake list + layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/intake/layout.tsx", [ + route( + ":workspaceSlug/projects/:projectId/intake", + "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/intake/page.tsx" + ), + ]), + ]), + + // Project Archives - Issues, Cycles, Modules + // Project Archives - Issues - List + layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(list)/layout.tsx", [ + route( + ":workspaceSlug/projects/:projectId/archives/issues", + "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(list)/page.tsx" + ), + ]), + + // Project Archives - Issues - Detail + layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/layout.tsx", [ + route( + ":workspaceSlug/projects/:projectId/archives/issues/:archivedIssueId", + "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/[archivedIssueId]/page.tsx" + ), + ]), + + // Project Archives - Cycles + layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/cycles/layout.tsx", [ + route( + ":workspaceSlug/projects/:projectId/archives/cycles", + "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/cycles/page.tsx" + ), + ]), + + // Project Archives - Modules + layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/modules/layout.tsx", [ + route( + ":workspaceSlug/projects/:projectId/archives/modules", + "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/modules/page.tsx" + ), ]), ]), @@ -320,44 +311,46 @@ export const coreRoutes: RouteConfigEntry[] = [ // -------------------------------------------------------------------- layout("./(all)/[workspaceSlug]/(settings)/settings/projects/layout.tsx", [ - // CORE Routes - // Project Settings + // No Projects available page route(":workspaceSlug/settings/projects", "./(all)/[workspaceSlug]/(settings)/settings/projects/page.tsx"), - route( - ":workspaceSlug/settings/projects/:projectId", - "./(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/page.tsx" - ), - // Project Members - route( - ":workspaceSlug/settings/projects/:projectId/members", - "./(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/members/page.tsx" - ), - // Project Features - route( - ":workspaceSlug/settings/projects/:projectId/features", - "./(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/features/page.tsx" - ), - // Project States - route( - ":workspaceSlug/settings/projects/:projectId/states", - "./(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/states/page.tsx" - ), - // Project Labels - route( - ":workspaceSlug/settings/projects/:projectId/labels", - "./(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/labels/page.tsx" - ), - // Project Estimates - route( - ":workspaceSlug/settings/projects/:projectId/estimates", - "./(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/estimates/page.tsx" - ), - // Project Automations - layout("./(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/automations/layout.tsx", [ + layout("./(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/layout.tsx", [ + // Project Settings route( - ":workspaceSlug/settings/projects/:projectId/automations", - "./(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/automations/page.tsx" + ":workspaceSlug/settings/projects/:projectId", + "./(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/page.tsx" ), + // Project Members + route( + ":workspaceSlug/settings/projects/:projectId/members", + "./(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/members/page.tsx" + ), + // Project Features + route( + ":workspaceSlug/settings/projects/:projectId/features", + "./(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/features/page.tsx" + ), + // Project States + route( + ":workspaceSlug/settings/projects/:projectId/states", + "./(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/states/page.tsx" + ), + // Project Labels + route( + ":workspaceSlug/settings/projects/:projectId/labels", + "./(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/labels/page.tsx" + ), + // Project Estimates + route( + ":workspaceSlug/settings/projects/:projectId/estimates", + "./(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/estimates/page.tsx" + ), + // Project Automations + layout("./(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/automations/layout.tsx", [ + route( + ":workspaceSlug/settings/projects/:projectId/automations", + "./(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/automations/page.tsx" + ), + ]), ]), ]), ]), diff --git a/apps/web/ce/layouts/project-wrapper.tsx b/apps/web/ce/layouts/project-wrapper.tsx index 73f596358..edfd3d41f 100644 --- a/apps/web/ce/layouts/project-wrapper.tsx +++ b/apps/web/ce/layouts/project-wrapper.tsx @@ -1,11 +1,10 @@ -import type { FC } from "react"; import { observer } from "mobx-react"; // layouts import { ProjectAuthWrapper as CoreProjectAuthWrapper } from "@/layouts/auth-layout/project-wrapper"; export type IProjectAuthWrapper = { workspaceSlug: string; - projectId?: string; + projectId: string; children: React.ReactNode; }; diff --git a/apps/web/core/components/auth-screens/project/join-project.tsx b/apps/web/core/components/auth-screens/project/join-project.tsx deleted file mode 100644 index 42b131610..000000000 --- a/apps/web/core/components/auth-screens/project/join-project.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { useState } from "react"; -import { useParams } from "next/navigation"; -import { ClipboardList } from "lucide-react"; -// plane imports -import { Button } from "@plane/propel/button"; -// assets -import Unauthorized from "@/app/assets/auth/unauthorized.svg?url"; -// hooks -import { useProject } from "@/hooks/store/use-project"; -import { useUserPermissions } from "@/hooks/store/user"; - -type Props = { - projectId?: string; - isPrivateProject?: boolean; -}; - -export function JoinProject(props: Props) { - const { projectId, isPrivateProject = false } = props; - // states - const [isJoiningProject, setIsJoiningProject] = useState(false); - // store hooks - const { joinProject } = useUserPermissions(); - const { fetchProjectDetails } = useProject(); - - const { workspaceSlug } = useParams(); - - const handleJoin = () => { - if (!workspaceSlug || !projectId) return; - - setIsJoiningProject(true); - - joinProject(workspaceSlug.toString(), projectId.toString()) - .then(() => fetchProjectDetails(workspaceSlug.toString(), projectId.toString())) - .finally(() => setIsJoiningProject(false)); - }; - - return ( -
-
- JoinProject -
-

- {!isPrivateProject ? `You are not a member of this project yet.` : `You are not a member of this project.`} -

- -
-

- {!isPrivateProject - ? `Click the button below to join it.` - : `This is a private project. \n We can't tell you more about this project to protect confidentiality.`} -

-
- {!isPrivateProject && ( -
- -
- )} -
- ); -} diff --git a/apps/web/core/components/auth-screens/project/project-access-restriction.tsx b/apps/web/core/components/auth-screens/project/project-access-restriction.tsx new file mode 100644 index 000000000..631afc5c4 --- /dev/null +++ b/apps/web/core/components/auth-screens/project/project-access-restriction.tsx @@ -0,0 +1,70 @@ +import { observer } from "mobx-react"; +// plane imports +import { useTranslation } from "@plane/i18n"; +import { EmptyStateDetailed } from "@plane/propel/empty-state"; + +type TProps = { + isWorkspaceAdmin: boolean; + handleJoinProject: () => void; + isJoinButtonDisabled: boolean; + errorStatusCode: number | undefined; +}; + +export const ProjectAccessRestriction = observer(function ProjectAccessRestriction(props: TProps) { + const { isWorkspaceAdmin, handleJoinProject, isJoinButtonDisabled, errorStatusCode } = props; + // plane hooks + const { t } = useTranslation(); + + // Show join project screen if: + // - User lacks project membership (409 Conflict) + // - User lacks permission to access the private project (403 Forbidden) but is a workspace admin (can join any project) + if (errorStatusCode === 409 || (errorStatusCode === 403 && isWorkspaceAdmin)) + return ( +
+ +
+ ); + + // Show no access screen if: + // - User lacks permission to access the private project (403 Forbidden) + if (errorStatusCode === 403) { + return ( +
+ +
+ ); + } + + // Show empty state screen if: + // - Project not found (404 Not Found) + // - Any other error status code + return ( +
+ +
+ ); +}); diff --git a/apps/web/core/components/issues/issue-layouts/roots/archived-issue-layout-root.tsx b/apps/web/core/components/issues/issue-layouts/roots/archived-issue-layout-root.tsx index 0a85e76c6..00e4ec2e6 100644 --- a/apps/web/core/components/issues/issue-layouts/roots/archived-issue-layout-root.tsx +++ b/apps/web/core/components/issues/issue-layouts/roots/archived-issue-layout-root.tsx @@ -6,7 +6,6 @@ import useSWR from "swr"; import { ISSUE_DISPLAY_FILTERS_BY_PAGE } from "@plane/constants"; import { EIssuesStoreType } from "@plane/types"; // components -import { LogoSpinner } from "@/components/common/logo-spinner"; import { ProjectLevelWorkItemFiltersHOC } from "@/components/work-item-filters/filters-hoc/project-level"; // hooks import { WorkItemFiltersRow } from "@/components/work-item-filters/filters-row"; @@ -26,7 +25,7 @@ export const ArchivedIssueLayoutRoot = observer(function ArchivedIssueLayoutRoot // derived values const workItemFilters = projectId ? issuesFilter?.getIssueFilters(projectId) : undefined; - const { isLoading } = useSWR( + useSWR( workspaceSlug && projectId ? `ARCHIVED_ISSUES_${workspaceSlug.toString()}_${projectId.toString()}` : null, async () => { if (workspaceSlug && projectId) { @@ -36,15 +35,7 @@ export const ArchivedIssueLayoutRoot = observer(function ArchivedIssueLayoutRoot { revalidateIfStale: false, revalidateOnFocus: false } ); - if (!workspaceSlug || !projectId) return <>; - - if (isLoading && !workItemFilters) - return ( -
- -
- ); - + if (!workspaceSlug || !projectId || !workItemFilters) return <>; return ( { if (workspaceSlug && projectId && cycleId) { @@ -78,15 +77,7 @@ export const CycleLayoutRoot = observer(function CycleLayoutRoot() { : 0; const canTransferIssues = isProgressSnapshotEmpty && transferableIssuesCount > 0; - if (!workspaceSlug || !projectId || !cycleId) return <>; - - if (isLoading && !workItemFilters) - return ( -
- -
- ); - + if (!workspaceSlug || !projectId || !cycleId || !workItemFilters) return <>; return ( ; - - if (isLoading && !workItemFilters) - return ( -
- -
- ); - + if (!workspaceSlug || !projectId || !moduleId || !workItemFilters) return <>; return ( { if (workspaceSlug && projectId) { @@ -59,15 +57,7 @@ export const ProjectLayoutRoot = observer(function ProjectLayoutRoot() { { revalidateIfStale: false, revalidateOnFocus: false } ); - if (!workspaceSlug || !projectId) return <>; - - if (isLoading && !workItemFilters) - return ( -
- -
- ); - + if (!workspaceSlug || !projectId || !workItemFilters) return <>; return ( { if (workspaceSlug && projectId && viewId) { @@ -78,16 +76,7 @@ export const ProjectViewLayoutRoot = observer(function ProjectViewLayoutRoot() { [issuesFilter, workspaceSlug, viewId] ); - if (!workspaceSlug || !projectId || !viewId) return <>; - - if (isLoading && !workItemFilters) { - return ( -
- -
- ); - } - + if (!workspaceSlug || !projectId || !viewId || !workItemFilters) return <>; return ( - -
- ); - + if (!issueIds || issueIds.length === 0) return <>; return (
diff --git a/apps/web/core/components/workspace/settings/workspace-details.tsx b/apps/web/core/components/workspace/settings/workspace-details.tsx index 26840134c..bb535b870 100644 --- a/apps/web/core/components/workspace/settings/workspace-details.tsx +++ b/apps/web/core/components/workspace/settings/workspace-details.tsx @@ -17,7 +17,6 @@ import type { IWorkspace } from "@plane/types"; import { CustomSelect, Input } from "@plane/ui"; import { copyUrlToClipboard, getFileURL } from "@plane/utils"; // components -import { LogoSpinner } from "@/components/common/logo-spinner"; import { WorkspaceImageUploadModal } from "@/components/core/modals/workspace-image-upload-modal"; // helpers import { captureError, captureSuccess } from "@/helpers/event-tracker.helper"; @@ -129,13 +128,7 @@ export const WorkspaceDetails = observer(function WorkspaceDetails() { const isAdmin = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.WORKSPACE); - if (!currentWorkspace) - return ( -
- -
- ); - + if (!currentWorkspace) return <>; return ( <> fetchProjectDetails(workspaceSlug, projectId) : null + const { isLoading: isProjectDetailsLoading, error: projectDetailsError } = useSWR( + PROJECT_DETAILS(workspaceSlug, projectId), + () => fetchProjectDetails(workspaceSlug, projectId) ); - + // fetching user project member information + useSWR(PROJECT_ME_INFORMATION(workspaceSlug, projectId), () => fetchUserProjectInfo(workspaceSlug, projectId)); // fetching project member preferences useSWR( - workspaceSlug && projectId && currentUserData?.id ? PROJECT_MEMBER_PREFERENCES(workspaceSlug, projectId) : null, - workspaceSlug && projectId && currentUserData?.id - ? () => fetchProjectMemberPreferences(workspaceSlug, projectId, currentUserData.id) - : null, + currentUserData?.id ? PROJECT_MEMBER_PREFERENCES(workspaceSlug, projectId) : null, + currentUserData?.id ? () => fetchProjectMemberPreferences(workspaceSlug, projectId, currentUserData.id) : null, { revalidateIfStale: false, revalidateOnFocus: false } ); // fetching project labels - useSWR( - workspaceSlug && projectId ? PROJECT_LABELS(workspaceSlug, projectId) : null, - workspaceSlug && projectId ? () => fetchProjectLabels(workspaceSlug, projectId) : null, - { revalidateIfStale: false, revalidateOnFocus: false } - ); + useSWR(PROJECT_LABELS(workspaceSlug, projectId), () => fetchProjectLabels(workspaceSlug, projectId), { + revalidateIfStale: false, + revalidateOnFocus: false, + }); // fetching project members - useSWR( - workspaceSlug && projectId ? PROJECT_MEMBERS(workspaceSlug, projectId) : null, - workspaceSlug && projectId ? () => fetchProjectMembers(workspaceSlug, projectId) : null, - { revalidateIfStale: false, revalidateOnFocus: false } - ); + useSWR(PROJECT_MEMBERS(workspaceSlug, projectId), () => fetchProjectMembers(workspaceSlug, projectId), { + revalidateIfStale: false, + revalidateOnFocus: false, + }); // fetching project states - useSWR( - workspaceSlug && projectId ? PROJECT_STATES(workspaceSlug, projectId) : null, - workspaceSlug && projectId ? () => fetchProjectStates(workspaceSlug, projectId) : null, - { revalidateIfStale: false, revalidateOnFocus: false } - ); + useSWR(PROJECT_STATES(workspaceSlug, projectId), () => fetchProjectStates(workspaceSlug, projectId), { + revalidateIfStale: false, + revalidateOnFocus: false, + }); // fetching project intake state - useSWR( - workspaceSlug && projectId ? PROJECT_INTAKE_STATE(workspaceSlug, projectId) : null, - workspaceSlug && projectId ? () => fetchProjectIntakeState(workspaceSlug, projectId) : null, - { revalidateIfStale: false, revalidateOnFocus: false } - ); + useSWR(PROJECT_INTAKE_STATE(workspaceSlug, projectId), () => fetchProjectIntakeState(workspaceSlug, projectId), { + revalidateIfStale: false, + revalidateOnFocus: false, + }); // fetching project estimates - useSWR( - workspaceSlug && projectId ? PROJECT_ESTIMATES(workspaceSlug, projectId) : null, - workspaceSlug && projectId ? () => getProjectEstimates(workspaceSlug, projectId) : null, - { revalidateIfStale: false, revalidateOnFocus: false } - ); + useSWR(PROJECT_ESTIMATES(workspaceSlug, projectId), () => getProjectEstimates(workspaceSlug, projectId), { + revalidateIfStale: false, + revalidateOnFocus: false, + }); // fetching project cycles - useSWR( - workspaceSlug && projectId ? PROJECT_ALL_CYCLES(workspaceSlug, projectId) : null, - workspaceSlug && projectId ? () => fetchAllCycles(workspaceSlug, projectId) : null, - { revalidateIfStale: false, revalidateOnFocus: false } - ); + useSWR(PROJECT_ALL_CYCLES(workspaceSlug, projectId), () => fetchAllCycles(workspaceSlug, projectId), { + revalidateIfStale: false, + revalidateOnFocus: false, + }); // fetching project modules useSWR( - workspaceSlug && projectId ? PROJECT_MODULES(workspaceSlug, projectId) : null, - workspaceSlug && projectId - ? async () => { - await fetchModulesSlim(workspaceSlug, projectId); - await fetchModules(workspaceSlug, projectId); - } - : null, + PROJECT_MODULES(workspaceSlug, projectId), + async () => { + await Promise.all([fetchModulesSlim(workspaceSlug, projectId), fetchModules(workspaceSlug, projectId)]); + }, { revalidateIfStale: false, revalidateOnFocus: false } ); // fetching project views - useSWR( - workspaceSlug && projectId ? PROJECT_VIEWS(workspaceSlug, projectId) : null, - workspaceSlug && projectId ? () => fetchViews(workspaceSlug, projectId) : null, - { revalidateIfStale: false, revalidateOnFocus: false } - ); + useSWR(PROJECT_VIEWS(workspaceSlug, projectId), () => fetchViews(workspaceSlug, projectId), { + revalidateIfStale: false, + revalidateOnFocus: false, + }); - // permissions - const canPerformEmptyStateActions = allowPermissions( - [EUserPermissions.ADMIN, EUserPermissions.MEMBER], - EUserPermissionsLevel.WORKSPACE - ); + // handle join project + const handleJoinProject = () => { + setIsJoiningProject(true); + joinProject(workspaceSlug, projectId) + .then(() => fetchProjectDetails(workspaceSlug, projectId)) + .finally(() => setIsJoiningProject(false)); + }; - // check if the project member apis is loading - if (isParentLoading || (!projectMemberInfo && projectId && hasPermissionToCurrentProject === null)) + const isProjectLoading = (isParentLoading || isProjectDetailsLoading) && !projectDetailsError; + + if (isProjectLoading) return null; + + if (!isProjectLoading && hasPermissionToCurrentProject === false) { return ( -
-
- -
-
- ); - - // check if the user don't have permission to access the project - if ( - ((projectExists?.network && projectExists?.network !== EProjectNetwork.PRIVATE) || isWorkspaceAdmin) && - projectId && - hasPermissionToCurrentProject === false - ) - return ; - - // check if the project info is not found. - if (loader === "loaded" && projectId && !!hasPermissionToCurrentProject === false) - return ( -
- { - toggleCreateProjectModal(true); - captureClick({ elementName: PROJECT_TRACKER_ELEMENTS.EMPTY_STATE_CREATE_PROJECT_BUTTON }); - }, - disabled: !canPerformEmptyStateActions, - variant: "primary", - }, - ]} - /> -
+ ); + } return <>{children}; }); diff --git a/apps/web/core/services/project/project.service.ts b/apps/web/core/services/project/project.service.ts index 266ffe95d..5915c7608 100644 --- a/apps/web/core/services/project/project.service.ts +++ b/apps/web/core/services/project/project.service.ts @@ -57,7 +57,7 @@ export class ProjectService extends APIService { return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/`) .then((response) => response?.data) .catch((error) => { - throw error?.response?.data; + throw error?.response; }); } diff --git a/packages/i18n/src/locales/cs/empty-state.ts b/packages/i18n/src/locales/cs/empty-state.ts index 4e552ea0b..452535d9d 100644 --- a/packages/i18n/src/locales/cs/empty-state.ts +++ b/packages/i18n/src/locales/cs/empty-state.ts @@ -24,6 +24,17 @@ export default { }, }, project_empty_state: { + no_access: { + title: "Vypadá to, že nemáte přístup k tomuto projektu", + restricted_description: "Kontaktujte administrátora a požádejte o přístup, abyste zde mohli pokračovat.", + join_description: "Klikněte na tlačítko níže pro připojení k projektu.", + cta_primary: "Připojit se k projektu", + cta_loading: "Připojování k projektu", + }, + invalid_project: { + title: "Projekt nebyl nalezen", + description: "Projekt, který hledáte, neexistuje.", + }, work_items: { title: "Začněte s vaší první pracovní položkou.", description: diff --git a/packages/i18n/src/locales/de/empty-state.ts b/packages/i18n/src/locales/de/empty-state.ts index f5b2fae25..55dfe5800 100644 --- a/packages/i18n/src/locales/de/empty-state.ts +++ b/packages/i18n/src/locales/de/empty-state.ts @@ -26,6 +26,17 @@ export default { }, }, project_empty_state: { + no_access: { + title: "Es scheint, als hätten Sie keinen Zugriff auf dieses Projekt", + restricted_description: "Kontaktieren Sie den Administrator, um Zugriff anzufordern, damit Sie hier fortfahren können.", + join_description: "Klicken Sie unten auf die Schaltfläche, um beizutreten.", + cta_primary: "Projekt beitreten", + cta_loading: "Projekt wird beigetreten", + }, + invalid_project: { + title: "Projekt nicht gefunden", + description: "Das gesuchte Projekt existiert nicht.", + }, work_items: { title: "Beginnen Sie mit Ihrem ersten Arbeitselement.", description: diff --git a/packages/i18n/src/locales/en/empty-state.ts b/packages/i18n/src/locales/en/empty-state.ts index 8ef996f4f..8f181883a 100644 --- a/packages/i18n/src/locales/en/empty-state.ts +++ b/packages/i18n/src/locales/en/empty-state.ts @@ -24,6 +24,17 @@ export default { }, }, project_empty_state: { + no_access: { + title: "Seems like you don’t have access to this Project", + restricted_description: "Contact admin to request for access and you can continue here.", + join_description: "Click the button below to join it.", + cta_primary: "Join project", + cta_loading: "Joining project", + }, + invalid_project: { + title: "Project not found", + description: "The project you are looking for does not exist.", + }, work_items: { title: "Start with your first work item.", description: diff --git a/packages/i18n/src/locales/es/empty-state.ts b/packages/i18n/src/locales/es/empty-state.ts index 0c062cdb4..6ef770152 100644 --- a/packages/i18n/src/locales/es/empty-state.ts +++ b/packages/i18n/src/locales/es/empty-state.ts @@ -26,6 +26,17 @@ export default { }, }, project_empty_state: { + no_access: { + title: "Parece que no tienes acceso a este proyecto", + restricted_description: "Contacta con el administrador para solicitar acceso y podrás continuar aquí.", + join_description: "Haz clic en el botón de abajo para unirte.", + cta_primary: "Unirse al proyecto", + cta_loading: "Uniéndose al proyecto", + }, + invalid_project: { + title: "Proyecto no encontrado", + description: "El proyecto que buscas no existe.", + }, work_items: { title: "Comienza con tu primer elemento de trabajo.", description: diff --git a/packages/i18n/src/locales/fr/empty-state.ts b/packages/i18n/src/locales/fr/empty-state.ts index 78c0202e1..3c7fc2816 100644 --- a/packages/i18n/src/locales/fr/empty-state.ts +++ b/packages/i18n/src/locales/fr/empty-state.ts @@ -27,6 +27,17 @@ export default { }, }, project_empty_state: { + no_access: { + title: "Il semble que vous n’ayez pas accès à ce projet", + restricted_description: "Contactez l’administrateur pour demander l’accès afin de pouvoir continuer ici.", + join_description: "Cliquez sur le bouton ci-dessous pour rejoindre le projet.", + cta_primary: "Rejoindre le projet", + cta_loading: "Rejoindre le projet…", + }, + invalid_project: { + title: "Projet non trouvé", + description: "Le projet que vous recherchez n’existe pas.", + }, work_items: { title: "Commencez avec votre premier élément de travail.", description: diff --git a/packages/i18n/src/locales/id/empty-state.ts b/packages/i18n/src/locales/id/empty-state.ts index 8224ec78e..11c900878 100644 --- a/packages/i18n/src/locales/id/empty-state.ts +++ b/packages/i18n/src/locales/id/empty-state.ts @@ -25,6 +25,17 @@ export default { }, }, project_empty_state: { + no_access: { + title: "Sepertinya Anda tidak memiliki akses ke Proyek ini", + restricted_description: "Hubungi admin untuk meminta akses agar Anda dapat melanjutkan di sini.", + join_description: "Klik tombol di bawah ini untuk bergabung.", + cta_primary: "Bergabung dengan proyek", + cta_loading: "Sedang bergabung dengan proyek", + }, + invalid_project: { + title: "Proyek tidak ditemukan", + description: "Proyek yang Anda cari tidak ada.", + }, work_items: { title: "Mulai dengan item kerja pertama Anda.", description: diff --git a/packages/i18n/src/locales/it/empty-state.ts b/packages/i18n/src/locales/it/empty-state.ts index 279241ebb..fa7a3ee84 100644 --- a/packages/i18n/src/locales/it/empty-state.ts +++ b/packages/i18n/src/locales/it/empty-state.ts @@ -27,6 +27,17 @@ export default { }, }, project_empty_state: { + no_access: { + title: "Sembra che tu non abbia accesso a questo progetto", + restricted_description: "Contatta l'amministratore per richiedere l'accesso e potrai continuare qui.", + join_description: "Clicca sul pulsante qui sotto per unirti.", + cta_primary: "Unisciti al progetto", + cta_loading: "Unione al progetto in corso", + }, + invalid_project: { + title: "Progetto non trovato", + description: "Il progetto che stai cercando non esiste.", + }, work_items: { title: "Inizia con il tuo primo elemento di lavoro.", description: diff --git a/packages/i18n/src/locales/ja/empty-state.ts b/packages/i18n/src/locales/ja/empty-state.ts index 6463c5ccf..0bf5ffbb2 100644 --- a/packages/i18n/src/locales/ja/empty-state.ts +++ b/packages/i18n/src/locales/ja/empty-state.ts @@ -24,6 +24,17 @@ export default { }, }, project_empty_state: { + no_access: { + title: "このプロジェクトへのアクセス権がないようです", + restricted_description: "管理者に連絡してアクセス権をリクエストすると、ここで作業を続けられます。", + join_description: "下のボタンをクリックして参加してください。", + cta_primary: "プロジェクトに参加", + cta_loading: "プロジェクトに参加中", + }, + invalid_project: { + title: "プロジェクトが見つかりません", + description: "お探しのプロジェクトは存在しません。", + }, work_items: { title: "最初の作業項目から始めましょう。", description: diff --git a/packages/i18n/src/locales/ko/empty-state.ts b/packages/i18n/src/locales/ko/empty-state.ts index 093091f65..cb769a180 100644 --- a/packages/i18n/src/locales/ko/empty-state.ts +++ b/packages/i18n/src/locales/ko/empty-state.ts @@ -24,6 +24,17 @@ export default { }, }, project_empty_state: { + no_access: { + title: "이 프로젝트에 접근할 수 없는 것 같습니다", + restricted_description: "관리자에게 접근 권한을 요청하시면 여기서 계속 진행하실 수 있습니다.", + join_description: "아래 버튼을 클릭하여 프로젝트에 참여하세요.", + cta_primary: "프로젝트 참여", + cta_loading: "프로젝트 참여 중", + }, + invalid_project: { + title: "프로젝트를 찾을 수 없습니다", + description: "찾으시는 프로젝트가 존재하지 않습니다.", + }, work_items: { title: "첫 번째 작업 항목으로 시작하세요.", description: diff --git a/packages/i18n/src/locales/pl/empty-state.ts b/packages/i18n/src/locales/pl/empty-state.ts index 30fc30a6a..7ab817673 100644 --- a/packages/i18n/src/locales/pl/empty-state.ts +++ b/packages/i18n/src/locales/pl/empty-state.ts @@ -24,6 +24,17 @@ export default { }, }, project_empty_state: { + no_access: { + title: "Wygląda na to, że nie masz dostępu do tego projektu", + restricted_description: "Skontaktuj się z administratorem, aby poprosić o dostęp i móc kontynuować tutaj.", + join_description: "Kliknij przycisk poniżej, aby dołączyć.", + cta_primary: "Dołącz do projektu", + cta_loading: "Dołączanie do projektu", + }, + invalid_project: { + title: "Projekt nie został znaleziony", + description: "Projekt, którego szukasz, nie istnieje.", + }, work_items: { title: "Zacznij od swojego pierwszego elementu roboczego.", description: diff --git a/packages/i18n/src/locales/pt-BR/empty-state.ts b/packages/i18n/src/locales/pt-BR/empty-state.ts index 8d92f4ff1..9ceb00fe5 100644 --- a/packages/i18n/src/locales/pt-BR/empty-state.ts +++ b/packages/i18n/src/locales/pt-BR/empty-state.ts @@ -26,6 +26,17 @@ export default { }, }, project_empty_state: { + no_access: { + title: "Parece que você não tem acesso a este projeto", + restricted_description: "Entre em contato com o administrador para solicitar acesso e você poderá continuar aqui.", + join_description: "Clique no botão abaixo para participar.", + cta_primary: "Participar do projeto", + cta_loading: "Participando do projeto", + }, + invalid_project: { + title: "Projeto não encontrado", + description: "O projeto que você está procurando não existe.", + }, work_items: { title: "Comece com seu primeiro item de trabalho.", description: diff --git a/packages/i18n/src/locales/ro/empty-state.ts b/packages/i18n/src/locales/ro/empty-state.ts index c123d82ab..7591096b1 100644 --- a/packages/i18n/src/locales/ro/empty-state.ts +++ b/packages/i18n/src/locales/ro/empty-state.ts @@ -25,6 +25,17 @@ export default { }, }, project_empty_state: { + no_access: { + title: "Se pare că nu aveți acces la acest proiect", + restricted_description: "Contactați administratorul pentru a solicita accesul și veți putea continua aici.", + join_description: "Faceți clic pe butonul de mai jos pentru a vă alătura.", + cta_primary: "Alăturați-vă proiectului", + cta_loading: "Se alătură proiectului", + }, + invalid_project: { + title: "Proiect negăsit", + description: "Proiectul pe care îl căutați nu există.", + }, work_items: { title: "Începeți cu primul dvs. element de lucru.", description: diff --git a/packages/i18n/src/locales/ru/empty-state.ts b/packages/i18n/src/locales/ru/empty-state.ts index eebe5d032..748ffb069 100644 --- a/packages/i18n/src/locales/ru/empty-state.ts +++ b/packages/i18n/src/locales/ru/empty-state.ts @@ -25,6 +25,17 @@ export default { }, }, project_empty_state: { + no_access: { + title: "Похоже, у вас нет доступа к этому проекту", + restricted_description: "Свяжитесь с администратором, чтобы запросить доступ, и вы сможете продолжить здесь.", + join_description: "Нажмите кнопку ниже, чтобы присоединиться.", + cta_primary: "Присоединиться к проекту", + cta_loading: "Присоединение к проекту", + }, + invalid_project: { + title: "Проект не найден", + description: "Проект, который вы ищете, не существует.", + }, work_items: { title: "Начните с вашего первого рабочего элемента.", description: diff --git a/packages/i18n/src/locales/sk/empty-state.ts b/packages/i18n/src/locales/sk/empty-state.ts index 29ccb63bd..b950fd701 100644 --- a/packages/i18n/src/locales/sk/empty-state.ts +++ b/packages/i18n/src/locales/sk/empty-state.ts @@ -24,6 +24,17 @@ export default { }, }, project_empty_state: { + no_access: { + title: "Zdá sa, že nemáte prístup k tomuto projektu", + restricted_description: "Kontaktujte administrátora, aby ste požiadali o prístup, a potom tu môžete pokračovať.", + join_description: "Kliknite na tlačidlo nižšie, aby ste sa pripojili.", + cta_primary: "Pripojiť sa k projektu", + cta_loading: "Pripájanie k projektu", + }, + invalid_project: { + title: "Projekt nebol nájdený", + description: "Projekt, ktorý hľadáte, neexistuje.", + }, work_items: { title: "Začnite s vašou prvou pracovnou položkou.", description: diff --git a/packages/i18n/src/locales/tr-TR/empty-state.ts b/packages/i18n/src/locales/tr-TR/empty-state.ts index e1f74039a..0f1a257ac 100644 --- a/packages/i18n/src/locales/tr-TR/empty-state.ts +++ b/packages/i18n/src/locales/tr-TR/empty-state.ts @@ -24,6 +24,17 @@ export default { }, }, project_empty_state: { + no_access: { + title: "Görünüşe göre bu projeye erişiminiz yok", + restricted_description: "Erişim talep etmek için yöneticiyle iletişime geçin, sonra burada devam edebilirsiniz.", + join_description: "Katılmak için aşağıdaki butona tıklayın.", + cta_primary: "Projeye katıl", + cta_loading: "Projeye katılınıyor", + }, + invalid_project: { + title: "Proje bulunamadı", + description: "Aradığınız proje mevcut değil.", + }, work_items: { title: "İlk iş öğenizle başlayın.", description: diff --git a/packages/i18n/src/locales/ua/empty-state.ts b/packages/i18n/src/locales/ua/empty-state.ts index 83dc3fb31..95ab8f790 100644 --- a/packages/i18n/src/locales/ua/empty-state.ts +++ b/packages/i18n/src/locales/ua/empty-state.ts @@ -25,6 +25,17 @@ export default { }, }, project_empty_state: { + no_access: { + title: "Схоже, у вас немає доступу до цього проєкту", + restricted_description: "Зверніться до адміністратора, щоб запросити доступ, і ви зможете продовжити тут.", + join_description: "Натисніть кнопку нижче, щоб приєднатися.", + cta_primary: "Приєднатися до проєкту", + cta_loading: "Приєднання до проєкту", + }, + invalid_project: { + title: "Проєкт не знайдено", + description: "Проєкт, який ви шукаєте, не існує.", + }, work_items: { title: "Почніть з вашого першого робочого елемента.", description: diff --git a/packages/i18n/src/locales/vi-VN/empty-state.ts b/packages/i18n/src/locales/vi-VN/empty-state.ts index 367b2f4c9..dbee0d4c1 100644 --- a/packages/i18n/src/locales/vi-VN/empty-state.ts +++ b/packages/i18n/src/locales/vi-VN/empty-state.ts @@ -25,6 +25,17 @@ export default { }, }, project_empty_state: { + no_access: { + title: "Có vẻ như bạn không có quyền truy cập vào Dự án này", + restricted_description: "Liên hệ với quản trị viên để yêu cầu quyền truy cập và bạn có thể tiếp tục tại đây.", + join_description: "Nhấn nút bên dưới để tham gia.", + cta_primary: "Tham gia dự án", + cta_loading: "Đang tham gia dự án", + }, + invalid_project: { + title: "Không tìm thấy dự án", + description: "Dự án bạn đang tìm kiếm không tồn tại.", + }, work_items: { title: "Bắt đầu với mục công việc đầu tiên của bạn.", description: diff --git a/packages/i18n/src/locales/zh-CN/empty-state.ts b/packages/i18n/src/locales/zh-CN/empty-state.ts index 67ffd5dde..c2028e2e2 100644 --- a/packages/i18n/src/locales/zh-CN/empty-state.ts +++ b/packages/i18n/src/locales/zh-CN/empty-state.ts @@ -24,6 +24,17 @@ export default { }, }, project_empty_state: { + no_access: { + title: "您似乎无权访问该项目", + restricted_description: "请联系管理员申请访问权限,通过后您可以在此继续。", + join_description: "点击下方按钮加入该项目。", + cta_primary: "加入项目", + cta_loading: "正在加入项目", + }, + invalid_project: { + title: "未找到项目", + description: "您查找的项目不存在。", + }, work_items: { title: "从您的第一个工作项开始。", description: "工作项是项目的构建块 — 分配负责人、设置优先级并轻松跟踪进度。", diff --git a/packages/i18n/src/locales/zh-TW/empty-state.ts b/packages/i18n/src/locales/zh-TW/empty-state.ts index 77e201bd1..8644cadf2 100644 --- a/packages/i18n/src/locales/zh-TW/empty-state.ts +++ b/packages/i18n/src/locales/zh-TW/empty-state.ts @@ -24,6 +24,17 @@ export default { }, }, project_empty_state: { + no_access: { + title: "您似乎無權存取此專案", + restricted_description: "請聯絡管理員以申請存取權,核准後即可在此繼續。", + join_description: "點擊下方按鈕加入專案。", + cta_primary: "加入專案", + cta_loading: "正在加入專案", + }, + invalid_project: { + title: "找不到專案", + description: "您所查找的專案不存在。", + }, work_items: { title: "從您的第一個工作項開始。", description: "工作項是專案的建構模組 — 指派負責人、設定優先順序並輕鬆追蹤進度。",