[WEB-5043] feat: web vite migration (#7973)

This commit is contained in:
Prateek Shourya 2025-11-06 14:08:48 +05:30 committed by GitHub
parent 118ecc81ba
commit 696fb96e87
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
642 changed files with 3013 additions and 2311 deletions

View file

@ -1,11 +1,11 @@
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import { Links, Meta, Outlet, Scripts } from "react-router"; import { Links, Meta, Outlet, Scripts } from "react-router";
import type { LinksFunction } from "react-router"; import type { LinksFunction } from "react-router";
import "../styles/globals.css";
import appleTouchIcon from "@/app/assets/favicon/apple-touch-icon.png?url"; import appleTouchIcon from "@/app/assets/favicon/apple-touch-icon.png?url";
import favicon16 from "@/app/assets/favicon/favicon-16x16.png?url"; import favicon16 from "@/app/assets/favicon/favicon-16x16.png?url";
import favicon32 from "@/app/assets/favicon/favicon-32x32.png?url"; import favicon32 from "@/app/assets/favicon/favicon-32x32.png?url";
import faviconIco from "@/app/assets/favicon/favicon.ico?url"; import faviconIco from "@/app/assets/favicon/favicon.ico?url";
import globalStyles from "@/styles/globals.css?url";
import type { Route } from "./+types/root"; import type { Route } from "./+types/root";
import { AppProviders } from "./providers"; import { AppProviders } from "./providers";
@ -19,6 +19,7 @@ export const links: LinksFunction = () => [
{ rel: "icon", type: "image/png", sizes: "16x16", href: favicon16 }, { rel: "icon", type: "image/png", sizes: "16x16", href: favicon16 },
{ rel: "shortcut icon", href: faviconIco }, { rel: "shortcut icon", href: faviconIco },
{ rel: "manifest", href: `/site.webmanifest.json` }, { rel: "manifest", href: `/site.webmanifest.json` },
{ rel: "stylesheet", href: globalStyles },
]; ];
export function Layout({ children }: { children: ReactNode }) { export function Layout({ children }: { children: ReactNode }) {
@ -56,6 +57,10 @@ export default function Root() {
return <Outlet />; return <Outlet />;
} }
export function HydrateFallback() {
return null;
}
export function ErrorBoundary() { export function ErrorBoundary() {
return ( return (
<div> <div>

View file

@ -1,16 +1,16 @@
"use client"; "use client";
import { redirect } from "react-router"; import { redirect } from "react-router";
import type { ClientLoaderFunctionArgs } from "react-router";
// plane imports // plane imports
import { SitesProjectPublishService } from "@plane/services"; import { SitesProjectPublishService } from "@plane/services";
import type { TProjectPublishSettings } from "@plane/types"; import type { TProjectPublishSettings } from "@plane/types";
// components // components
import { LogoSpinner } from "@/components/common/logo-spinner"; import { LogoSpinner } from "@/components/common/logo-spinner";
import type { Route } from "./+types/page";
const publishService = new SitesProjectPublishService(); const publishService = new SitesProjectPublishService();
export const clientLoader = async ({ params, request }: ClientLoaderFunctionArgs) => { export const clientLoader = async ({ params, request }: Route.ClientLoaderArgs) => {
const { workspaceSlug, projectId } = params; const { workspaceSlug, projectId } = params;
// Validate required params // Validate required params

View file

@ -1,12 +1,11 @@
import { Links, Meta, Outlet, Scripts } from "react-router"; import { Links, Meta, Outlet, Scripts } from "react-router";
import type { LinksFunction } from "react-router"; import type { LinksFunction } from "react-router";
// styles
import "@/styles/globals.css";
// assets // assets
import appleTouchIcon from "@/app/assets/favicon/apple-touch-icon.png?url"; import appleTouchIcon from "@/app/assets/favicon/apple-touch-icon.png?url";
import favicon16 from "@/app/assets/favicon/favicon-16x16.png?url"; import favicon16 from "@/app/assets/favicon/favicon-16x16.png?url";
import favicon32 from "@/app/assets/favicon/favicon-32x32.png?url"; import favicon32 from "@/app/assets/favicon/favicon-32x32.png?url";
import faviconIco from "@/app/assets/favicon/favicon.ico?url"; import faviconIco from "@/app/assets/favicon/favicon.ico?url";
import globalStyles from "@/styles/globals.css?url";
// types // types
import type { Route } from "./+types/root"; import type { Route } from "./+types/root";
// local imports // local imports
@ -22,6 +21,7 @@ export const links: LinksFunction = () => [
{ rel: "icon", type: "image/png", sizes: "16x16", href: favicon16 }, { rel: "icon", type: "image/png", sizes: "16x16", href: favicon16 },
{ rel: "shortcut icon", href: faviconIco }, { rel: "shortcut icon", href: faviconIco },
{ rel: "manifest", href: `/site.webmanifest.json` }, { rel: "manifest", href: `/site.webmanifest.json` },
{ rel: "stylesheet", href: globalStyles },
]; ];
export function Layout({ children }: { children: React.ReactNode }) { export function Layout({ children }: { children: React.ReactNode }) {
@ -61,6 +61,10 @@ export default function Root() {
return <Outlet />; return <Outlet />;
} }
export function HydrateFallback() {
return null;
}
export function ErrorBoundary() { export function ErrorBoundary() {
return <ErrorPage />; return <ErrorPage />;
} }

View file

@ -20,7 +20,7 @@
"@bprogress/core": "catalog:", "@bprogress/core": "catalog:",
"@emotion/react": "^11.11.1", "@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0", "@emotion/styled": "^11.11.0",
"@headlessui/react": "^1.7.13", "@headlessui/react": "^1.7.19",
"@plane/constants": "workspace:*", "@plane/constants": "workspace:*",
"@plane/editor": "workspace:*", "@plane/editor": "workspace:*",
"@plane/i18n": "workspace:*", "@plane/i18n": "workspace:*",

11
apps/web/.dockerignore Normal file
View file

@ -0,0 +1,11 @@
node_modules
.next
.react-router
.vite
.turbo
build
dist
*.log
.env*
!.env.example

View file

@ -1,4 +1,7 @@
.next/* .next/*
.react-router/*
.vite/*
build/*
out/* out/*
public/* public/*
core/local-db/worker/wa-sqlite/src/* core/local-db/worker/wa-sqlite/src/*

View file

@ -1,6 +1,7 @@
module.exports = { module.exports = {
root: true, root: true,
extends: ["@plane/eslint-config/next.js"], extends: ["@plane/eslint-config/next.js"],
ignorePatterns: ["build/**", "dist/**", ".vite/**"],
rules: { rules: {
"no-duplicate-imports": "off", "no-duplicate-imports": "off",
"import/no-duplicates": ["error", { "prefer-inline": false }], "import/no-duplicates": ["error", { "prefer-inline": false }],

View file

@ -1,5 +1,9 @@
.next .next
.react-router
.vite
.turbo .turbo
out/ out/
dist/ dist/
build/ build/
node_modules
pnpm-lock.yaml

View file

@ -71,50 +71,16 @@ ENV TURBO_TELEMETRY_DISABLED=1
RUN pnpm turbo run build --filter=web RUN pnpm turbo run build --filter=web
# ***************************************************************************** # *****************************************************************************
# STAGE 3: Copy the project and start it # STAGE 3: Serve with nginx
# ***************************************************************************** # *****************************************************************************
FROM base AS runner FROM nginx:1.27-alpine AS production
WORKDIR /app
# Don't run production as root COPY apps/web/nginx/nginx.conf /etc/nginx/nginx.conf
RUN addgroup --system --gid 1001 nodejs COPY --from=installer /app/apps/web/build/client /usr/share/nginx/html
RUN adduser --system --uid 1001 nextjs
USER nextjs
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=installer /app/apps/web/.next/standalone ./
COPY --from=installer /app/apps/web/.next/static ./apps/web/.next/static
COPY --from=installer /app/apps/web/public ./apps/web/public
ARG NEXT_PUBLIC_API_BASE_URL=""
ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL
ARG NEXT_PUBLIC_ADMIN_BASE_URL=""
ENV NEXT_PUBLIC_ADMIN_BASE_URL=$NEXT_PUBLIC_ADMIN_BASE_URL
ARG NEXT_PUBLIC_ADMIN_BASE_PATH="/god-mode"
ENV NEXT_PUBLIC_ADMIN_BASE_PATH=$NEXT_PUBLIC_ADMIN_BASE_PATH
ARG NEXT_PUBLIC_LIVE_BASE_URL=""
ENV NEXT_PUBLIC_LIVE_BASE_URL=$NEXT_PUBLIC_LIVE_BASE_URL
ARG NEXT_PUBLIC_LIVE_BASE_PATH="/live"
ENV NEXT_PUBLIC_LIVE_BASE_PATH=$NEXT_PUBLIC_LIVE_BASE_PATH
ARG NEXT_PUBLIC_SPACE_BASE_URL=""
ENV NEXT_PUBLIC_SPACE_BASE_URL=$NEXT_PUBLIC_SPACE_BASE_URL
ARG NEXT_PUBLIC_SPACE_BASE_PATH="/spaces"
ENV NEXT_PUBLIC_SPACE_BASE_PATH=$NEXT_PUBLIC_SPACE_BASE_PATH
ARG NEXT_PUBLIC_WEB_BASE_URL=""
ENV NEXT_PUBLIC_WEB_BASE_URL=$NEXT_PUBLIC_WEB_BASE_URL
ENV NEXT_TELEMETRY_DISABLED=1
ENV TURBO_TELEMETRY_DISABLED=1
EXPOSE 3000 EXPOSE 3000
CMD ["node", "apps/web/server.js"] HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD curl -fsS http://127.0.0.1:3000/ >/dev/null || exit 1
CMD ["nginx", "-g", "daemon off;"]

View file

@ -1,16 +1,19 @@
"use client"; "use client";
// components // components
import { Outlet } from "react-router";
import { AppHeader } from "@/components/core/app-header"; import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper"; import { ContentWrapper } from "@/components/core/content-wrapper";
// local imports // local imports
import { WorkspaceActiveCycleHeader } from "./header"; import { WorkspaceActiveCycleHeader } from "./header";
export default function WorkspaceActiveCycleLayout({ children }: { children: React.ReactNode }) { export default function WorkspaceActiveCycleLayout() {
return ( return (
<> <>
<AppHeader header={<WorkspaceActiveCycleHeader />} /> <AppHeader header={<WorkspaceActiveCycleHeader />} />
<ContentWrapper>{children}</ContentWrapper> <ContentWrapper>
<Outlet />
</ContentWrapper>
</> </>
); );
} }

View file

@ -8,7 +8,7 @@ import { useWorkspace } from "@/hooks/store/use-workspace";
// plane web components // plane web components
import { WorkspaceActiveCyclesRoot } from "@/plane-web/components/active-cycles"; import { WorkspaceActiveCyclesRoot } from "@/plane-web/components/active-cycles";
const WorkspaceActiveCyclesPage = observer(() => { function WorkspaceActiveCyclesPage() {
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
// derived values // derived values
const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Active Cycles` : undefined; const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Active Cycles` : undefined;
@ -19,6 +19,6 @@ const WorkspaceActiveCyclesPage = observer(() => {
<WorkspaceActiveCyclesRoot /> <WorkspaceActiveCyclesRoot />
</> </>
); );
}); }
export default WorkspaceActiveCyclesPage; export default observer(WorkspaceActiveCyclesPage);

View file

@ -1,14 +1,17 @@
"use client"; "use client";
// components // components
import { Outlet } from "react-router";
import { AppHeader } from "@/components/core/app-header"; import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper"; import { ContentWrapper } from "@/components/core/content-wrapper";
import { WorkspaceAnalyticsHeader } from "./header"; import { WorkspaceAnalyticsHeader } from "./header";
export default function WorkspaceAnalyticsTabLayout({ children }: { children: React.ReactNode }) { export default function WorkspaceAnalyticsTabLayout() {
return ( return (
<> <>
<AppHeader header={<WorkspaceAnalyticsHeader />} /> <AppHeader header={<WorkspaceAnalyticsHeader />} />
<ContentWrapper>{children}</ContentWrapper> <ContentWrapper>
<Outlet />
</ContentWrapper>
</> </>
); );
} }

View file

@ -19,17 +19,9 @@ import { useProject } from "@/hooks/store/use-project";
import { useWorkspace } from "@/hooks/store/use-workspace"; import { useWorkspace } from "@/hooks/store/use-workspace";
import { useUserPermissions } from "@/hooks/store/user"; import { useUserPermissions } from "@/hooks/store/user";
import { getAnalyticsTabs } from "@/plane-web/components/analytics/tabs"; import { getAnalyticsTabs } from "@/plane-web/components/analytics/tabs";
import type { Route } from "./+types/page";
type Props = { function AnalyticsPage({ params }: Route.ComponentProps) {
params: {
tabId: string;
workspaceSlug: string;
};
};
const AnalyticsPage = observer((props: Props) => {
// props
const { params } = props;
const { tabId } = params; const { tabId } = params;
// hooks // hooks
@ -68,7 +60,7 @@ const AnalyticsPage = observer((props: Props) => {
})), })),
[ANALYTICS_TABS, router, currentWorkspace?.slug] [ANALYTICS_TABS, router, currentWorkspace?.slug]
); );
const defaultTab = tabId || ANALYTICS_TABS[0].key; const defaultTab = tabId;
return ( return (
<> <>
@ -111,6 +103,6 @@ const AnalyticsPage = observer((props: Props) => {
)} )}
</> </>
); );
}); }
export default AnalyticsPage; export default observer(AnalyticsPage);

View file

@ -1,15 +1,18 @@
"use client"; "use client";
// components // components
import { Outlet } from "react-router";
import { AppHeader } from "@/components/core/app-header"; import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper"; import { ContentWrapper } from "@/components/core/content-wrapper";
import { ProjectIssueDetailsHeader } from "./header"; import { ProjectIssueDetailsHeader } from "./header";
export default function ProjectIssueDetailsLayout({ children }: { children: React.ReactNode }) { export default function ProjectIssueDetailsLayout() {
return ( return (
<> <>
<AppHeader header={<ProjectIssueDetailsHeader />} /> <AppHeader header={<ProjectIssueDetailsHeader />} />
<ContentWrapper className="overflow-hidden">{children}</ContentWrapper> <ContentWrapper className="overflow-hidden">
<Outlet />
</ContentWrapper>
</> </>
); );
} }

View file

@ -1,14 +1,16 @@
"use client"; "use client";
import React, { useEffect } from "react"; import { useEffect } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import { useTheme } from "next-themes"; import { useTheme } from "next-themes";
import useSWR from "swr"; import useSWR from "swr";
// plane imports // plane imports
import { useTranslation } from "@plane/i18n"; import { useTranslation } from "@plane/i18n";
import { EIssueServiceType } from "@plane/types"; import { EIssueServiceType } from "@plane/types";
import { Loader } from "@plane/ui"; import { Loader } from "@plane/ui";
// assets
import emptyIssueDark from "@/app/assets/empty-state/search/issues-dark.webp?url";
import emptyIssueLight from "@/app/assets/empty-state/search/issues-light.webp?url";
// components // components
import { EmptyState } from "@/components/common/empty-state"; import { EmptyState } from "@/components/common/empty-state";
import { PageHead } from "@/components/core/page-title"; import { PageHead } from "@/components/core/page-title";
@ -17,17 +19,16 @@ import { IssueDetailRoot } from "@/components/issues/issue-detail";
import { useAppTheme } from "@/hooks/store/use-app-theme"; import { useAppTheme } from "@/hooks/store/use-app-theme";
import { useIssueDetail } from "@/hooks/store/use-issue-detail"; import { useIssueDetail } from "@/hooks/store/use-issue-detail";
import { useProject } from "@/hooks/store/use-project"; import { useProject } from "@/hooks/store/use-project";
// assets
import { useAppRouter } from "@/hooks/use-app-router"; import { useAppRouter } from "@/hooks/use-app-router";
// plane web imports
import { useWorkItemProperties } from "@/plane-web/hooks/use-issue-properties"; import { useWorkItemProperties } from "@/plane-web/hooks/use-issue-properties";
import { ProjectAuthWrapper } from "@/plane-web/layouts/project-wrapper"; import { ProjectAuthWrapper } from "@/plane-web/layouts/project-wrapper";
import emptyIssueDark from "@/public/empty-state/search/issues-dark.webp"; import type { Route } from "./+types/page";
import emptyIssueLight from "@/public/empty-state/search/issues-light.webp";
const IssueDetailsPage = observer(() => { function IssueDetailsPage({ params }: Route.ComponentProps) {
// router // router
const router = useAppRouter(); const router = useAppRouter();
const { workspaceSlug, workItem } = useParams(); const { workspaceSlug, workItem } = params;
// hooks // hooks
const { resolvedTheme } = useTheme(); const { resolvedTheme } = useTheme();
// store hooks // store hooks
@ -39,15 +40,11 @@ const IssueDetailsPage = observer(() => {
const { getProjectById } = useProject(); const { getProjectById } = useProject();
const { toggleIssueDetailSidebar, issueDetailSidebarCollapsed } = useAppTheme(); const { toggleIssueDetailSidebar, issueDetailSidebarCollapsed } = useAppTheme();
const projectIdentifier = workItem?.toString().split("-")[0]; const [projectIdentifier, sequence_id] = workItem.split("-");
const sequence_id = workItem?.toString().split("-")[1];
// fetching issue details // fetching issue details
const { data, isLoading, error } = useSWR( const { data, isLoading, error } = useSWR(`ISSUE_DETAIL_${workspaceSlug}_${projectIdentifier}_${sequence_id}`, () =>
workspaceSlug && workItem ? `ISSUE_DETAIL_${workspaceSlug}_${projectIdentifier}_${sequence_id}` : null, fetchIssueWithIdentifier(workspaceSlug.toString(), projectIdentifier, sequence_id)
workspaceSlug && workItem
? () => fetchIssueWithIdentifier(workspaceSlug.toString(), projectIdentifier, sequence_id)
: null
); );
const issueId = data?.id; const issueId = data?.id;
const projectId = data?.project_id; const projectId = data?.project_id;
@ -113,14 +110,13 @@ const IssueDetailsPage = observer(() => {
</div> </div>
</Loader> </Loader>
) : ( ) : (
workspaceSlug &&
projectId && projectId &&
issueId && ( issueId && (
<ProjectAuthWrapper workspaceSlug={workspaceSlug?.toString()} projectId={projectId?.toString()}> <ProjectAuthWrapper workspaceSlug={workspaceSlug} projectId={projectId}>
<IssueDetailRoot <IssueDetailRoot
workspaceSlug={workspaceSlug.toString()} workspaceSlug={workspaceSlug}
projectId={projectId.toString()} projectId={projectId}
issueId={issueId.toString()} issueId={issueId}
is_archived={!!issue?.archived_at} is_archived={!!issue?.archived_at}
/> />
</ProjectAuthWrapper> </ProjectAuthWrapper>
@ -128,6 +124,6 @@ const IssueDetailsPage = observer(() => {
)} )}
</> </>
); );
}); }
export default IssueDetailsPage; export default observer(IssueDetailsPage);

View file

@ -1,16 +1,19 @@
"use client"; "use client";
// components // components
import { Outlet } from "react-router";
import { AppHeader } from "@/components/core/app-header"; import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper"; import { ContentWrapper } from "@/components/core/content-wrapper";
// local imports // local imports
import { WorkspaceDraftHeader } from "./header"; import { WorkspaceDraftHeader } from "./header";
export default function WorkspaceDraftLayout({ children }: { children: React.ReactNode }) { export default function WorkspaceDraftLayout() {
return ( return (
<> <>
<AppHeader header={<WorkspaceDraftHeader />} /> <AppHeader header={<WorkspaceDraftHeader />} />
<ContentWrapper>{children}</ContentWrapper> <ContentWrapper>
<Outlet />
</ContentWrapper>
</> </>
); );
} }

View file

@ -1,19 +1,14 @@
"use client"; "use client";
import { useParams } from "next/navigation";
// components // components
import { PageHead } from "@/components/core/page-title"; import { PageHead } from "@/components/core/page-title";
import { WorkspaceDraftIssuesRoot } from "@/components/issues/workspace-draft"; import { WorkspaceDraftIssuesRoot } from "@/components/issues/workspace-draft";
import type { Route } from "./+types/page";
const WorkspaceDraftPage = () => { function WorkspaceDraftPage({ params }: Route.ComponentProps) {
// router const { workspaceSlug } = params;
const { workspaceSlug: routeWorkspaceSlug } = useParams();
const pageTitle = "Workspace Draft"; const pageTitle = "Workspace Draft";
// derived values
const workspaceSlug = (routeWorkspaceSlug as string) || undefined;
if (!workspaceSlug) return null;
return ( return (
<> <>
<PageHead title={pageTitle} /> <PageHead title={pageTitle} />
@ -22,6 +17,6 @@ const WorkspaceDraftPage = () => {
</div> </div>
</> </>
); );
}; }
export default WorkspaceDraftPage; export default WorkspaceDraftPage;

View file

@ -1,14 +1,16 @@
"use client"; "use client";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { Outlet } from "react-router";
import { ProjectsAppPowerKProvider } from "@/components/power-k/projects-app-provider"; import { ProjectsAppPowerKProvider } from "@/components/power-k/projects-app-provider";
import { AuthenticationWrapper } from "@/lib/wrappers/authentication-wrapper"; import { AuthenticationWrapper } from "@/lib/wrappers/authentication-wrapper";
// plane web components // plane web components
import { WorkspaceAuthWrapper } from "@/plane-web/layouts/workspace-wrapper"; import { WorkspaceAuthWrapper } from "@/plane-web/layouts/workspace-wrapper";
import { ProjectAppSidebar } from "./_sidebar"; import { ProjectAppSidebar } from "./_sidebar";
const WorkspaceLayoutContent = observer(({ children }: { children: React.ReactNode }) => ( function WorkspaceLayout() {
<> return (
<AuthenticationWrapper>
<ProjectsAppPowerKProvider /> <ProjectsAppPowerKProvider />
<WorkspaceAuthWrapper> <WorkspaceAuthWrapper>
<div className="relative flex flex-col h-full w-full overflow-hidden rounded-lg border border-custom-border-200"> <div className="relative flex flex-col h-full w-full overflow-hidden rounded-lg border border-custom-border-200">
@ -16,18 +18,13 @@ const WorkspaceLayoutContent = observer(({ children }: { children: React.ReactNo
<div className="relative flex size-full overflow-hidden"> <div className="relative flex size-full overflow-hidden">
<ProjectAppSidebar /> <ProjectAppSidebar />
<main className="relative flex h-full w-full flex-col overflow-hidden bg-custom-background-100"> <main className="relative flex h-full w-full flex-col overflow-hidden bg-custom-background-100">
{children} <Outlet />
</main> </main>
</div> </div>
</div> </div>
</WorkspaceAuthWrapper> </WorkspaceAuthWrapper>
</>
));
export default function WorkspaceLayout({ children }: { children: React.ReactNode }) {
return (
<AuthenticationWrapper>
<WorkspaceLayoutContent>{children}</WorkspaceLayoutContent>
</AuthenticationWrapper> </AuthenticationWrapper>
); );
} }
export default observer(WorkspaceLayout);

View file

@ -1,13 +1,16 @@
"use client"; "use client";
import { Outlet } from "react-router";
// components // components
import { NotificationsSidebarRoot } from "@/components/workspace-notifications/sidebar"; import { NotificationsSidebarRoot } from "@/components/workspace-notifications/sidebar";
export default function ProjectInboxIssuesLayout({ children }: { children: React.ReactNode }) { export default function ProjectInboxIssuesLayout() {
return ( return (
<div className="relative w-full h-full overflow-hidden flex items-center"> <div className="relative w-full h-full overflow-hidden flex items-center">
<NotificationsSidebarRoot /> <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> </div>
); );
} }

View file

@ -1,7 +1,6 @@
"use client"; "use client";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// plane imports // plane imports
import { useTranslation } from "@plane/i18n"; import { useTranslation } from "@plane/i18n";
// components // components
@ -9,9 +8,10 @@ import { PageHead } from "@/components/core/page-title";
import { NotificationsRoot } from "@/components/workspace-notifications"; import { NotificationsRoot } from "@/components/workspace-notifications";
// hooks // hooks
import { useWorkspace } from "@/hooks/store/use-workspace"; import { useWorkspace } from "@/hooks/store/use-workspace";
import type { Route } from "./+types/page";
const WorkspaceDashboardPage = observer(() => { function WorkspaceDashboardPage({ params }: Route.ComponentProps) {
const { workspaceSlug } = useParams(); const { workspaceSlug } = params;
// plane hooks // plane hooks
const { t } = useTranslation(); const { t } = useTranslation();
// hooks // hooks
@ -24,9 +24,9 @@ const WorkspaceDashboardPage = observer(() => {
return ( return (
<> <>
<PageHead title={pageTitle} /> <PageHead title={pageTitle} />
<NotificationsRoot workspaceSlug={workspaceSlug?.toString()} /> <NotificationsRoot workspaceSlug={workspaceSlug} />
</> </>
); );
}); }
export default WorkspaceDashboardPage; export default observer(WorkspaceDashboardPage);

View file

@ -12,7 +12,7 @@ import { useWorkspace } from "@/hooks/store/use-workspace";
// local components // local components
import { WorkspaceDashboardHeader } from "./header"; import { WorkspaceDashboardHeader } from "./header";
const WorkspaceDashboardPage = observer(() => { function WorkspaceDashboardPage() {
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
const { t } = useTranslation(); const { t } = useTranslation();
// derived values // derived values
@ -27,6 +27,6 @@ const WorkspaceDashboardPage = observer(() => {
</ContentWrapper> </ContentWrapper>
</> </>
); );
}); }
export default WorkspaceDashboardPage; export default observer(WorkspaceDashboardPage);

View file

@ -1,10 +1,10 @@
"use client"; "use client";
import React from "react"; import React from "react";
import { useParams } from "next/navigation";
// components // components
import { PageHead } from "@/components/core/page-title"; import { PageHead } from "@/components/core/page-title";
import { ProfileIssuesPage } from "@/components/profile/profile-issues"; import { ProfileIssuesPage } from "@/components/profile/profile-issues";
import type { Route } from "./+types/page";
const ProfilePageHeader = { const ProfilePageHeader = {
assigned: "Profile - Assigned", assigned: "Profile - Assigned",
@ -12,10 +12,14 @@ const ProfilePageHeader = {
subscribed: "Profile - Subscribed", subscribed: "Profile - Subscribed",
}; };
const ProfileIssuesTypePage = () => { function isValidProfileViewId(viewId: string): viewId is keyof typeof ProfilePageHeader {
const { profileViewId } = useParams() as { profileViewId: "assigned" | "subscribed" | "created" | undefined }; return viewId in ProfilePageHeader;
}
if (!profileViewId) return null; function ProfileIssuesTypePage({ params }: Route.ComponentProps) {
const { profileViewId } = params;
if (!isValidProfileViewId(profileViewId)) return null;
const header = ProfilePageHeader[profileViewId]; const header = ProfilePageHeader[profileViewId];
@ -25,6 +29,6 @@ const ProfileIssuesTypePage = () => {
<ProfileIssuesPage type={profileViewId} /> <ProfileIssuesPage type={profileViewId} />
</> </>
); );
}; }
export default ProfileIssuesTypePage; export default ProfileIssuesTypePage;

View file

@ -15,7 +15,7 @@ import { useUserPermissions } from "@/hooks/store/user";
const PER_PAGE = 100; const PER_PAGE = 100;
const ProfileActivityPage = observer(() => { function ProfileActivityPage() {
// states // states
const [pageCount, setPageCount] = useState(1); const [pageCount, setPageCount] = useState(1);
const [totalPages, setTotalPages] = useState(0); const [totalPages, setTotalPages] = useState(0);
@ -69,6 +69,6 @@ const ProfileActivityPage = observer(() => {
</div> </div>
</> </>
); );
}); }
export default ProfileActivityPage; export default observer(ProfileActivityPage);

View file

@ -1,7 +1,8 @@
"use client"; "use client";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useParams, usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
import { Outlet } from "react-router";
import useSWR from "swr"; import useSWR from "swr";
// components // components
import { PROFILE_VIEWER_TAB, PROFILE_ADMINS_TAB, EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { PROFILE_VIEWER_TAB, PROFILE_ADMINS_TAB, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
@ -16,20 +17,16 @@ import { useUserPermissions } from "@/hooks/store/user";
import useSize from "@/hooks/use-window-size"; import useSize from "@/hooks/use-window-size";
// local components // local components
import { UserService } from "@/services/user.service"; import { UserService } from "@/services/user.service";
import type { Route } from "./+types/layout";
import { UserProfileHeader } from "./header"; import { UserProfileHeader } from "./header";
import { ProfileIssuesMobileHeader } from "./mobile-header"; import { ProfileIssuesMobileHeader } from "./mobile-header";
import { ProfileNavbar } from "./navbar"; import { ProfileNavbar } from "./navbar";
const userService = new UserService(); const userService = new UserService();
type Props = { function UseProfileLayout({ params }: Route.ComponentProps) {
children: React.ReactNode;
};
const UseProfileLayout: React.FC<Props> = observer((props) => {
const { children } = props;
// router // router
const { workspaceSlug, userId } = useParams(); const { workspaceSlug, userId } = params;
const pathname = usePathname(); const pathname = usePathname();
// store hooks // store hooks
const { allowPermissions } = useUserPermissions(); const { allowPermissions } = useUserPermissions();
@ -43,11 +40,8 @@ const UseProfileLayout: React.FC<Props> = observer((props) => {
const windowSize = useSize(); const windowSize = useSize();
const isSmallerScreen = windowSize[0] >= 768; const isSmallerScreen = windowSize[0] >= 768;
const { data: userProjectsData } = useSWR( const { data: userProjectsData } = useSWR(USER_PROFILE_PROJECT_SEGREGATION(workspaceSlug, userId), () =>
workspaceSlug && userId ? USER_PROFILE_PROJECT_SEGREGATION(workspaceSlug.toString(), userId.toString()) : null, userService.getUserProfileProjectsSegregation(workspaceSlug, userId)
workspaceSlug && userId
? () => userService.getUserProfileProjectsSegregation(workspaceSlug.toString(), userId.toString())
: null
); );
// derived values // derived values
const isAuthorizedPath = const isAuthorizedPath =
@ -78,7 +72,9 @@ const UseProfileLayout: React.FC<Props> = observer((props) => {
<div className="flex w-full flex-col md:h-full md:overflow-hidden"> <div className="flex w-full flex-col md:h-full md:overflow-hidden">
<ProfileNavbar isAuthorized={!!isAuthorized} /> <ProfileNavbar isAuthorized={!!isAuthorized} />
{isAuthorized || !isAuthorizedPath ? ( {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"> <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")} {t("you_do_not_have_the_permission_to_access_this_page")}
@ -93,6 +89,6 @@ const UseProfileLayout: React.FC<Props> = observer((props) => {
</div> </div>
</> </>
); );
}); }
export default UseProfileLayout; export default observer(UseProfileLayout);

View file

@ -1,6 +1,5 @@
"use client"; "use client";
import { useParams } from "next/navigation";
import useSWR from "swr"; import useSWR from "swr";
// plane imports // plane imports
import { GROUP_CHOICES } from "@plane/constants"; import { GROUP_CHOICES } from "@plane/constants";
@ -18,15 +17,15 @@ import { ProfileWorkload } from "@/components/profile/overview/workload";
import { USER_PROFILE_DATA } from "@/constants/fetch-keys"; import { USER_PROFILE_DATA } from "@/constants/fetch-keys";
// services // services
import { UserService } from "@/services/user.service"; import { UserService } from "@/services/user.service";
import type { Route } from "./+types/page";
const userService = new UserService(); const userService = new UserService();
export default function ProfileOverviewPage() { export default function ProfileOverviewPage({ params }: Route.ComponentProps) {
const { workspaceSlug, userId } = useParams(); const { workspaceSlug, userId } = params;
const { t } = useTranslation(); const { t } = useTranslation();
const { data: userProfile } = useSWR( const { data: userProfile } = useSWR(USER_PROFILE_DATA(workspaceSlug, userId), () =>
workspaceSlug && userId ? USER_PROFILE_DATA(workspaceSlug.toString(), userId.toString()) : null, userService.getUserProfileData(workspaceSlug, userId)
workspaceSlug && userId ? () => userService.getUserProfileData(workspaceSlug.toString(), userId.toString()) : null
); );
const stateDistribution: IUserStateDistribution[] = Object.keys(GROUP_CHOICES).map((key) => { const stateDistribution: IUserStateDistribution[] = Object.keys(GROUP_CHOICES).map((key) => {

View file

@ -1,15 +1,18 @@
"use client"; "use client";
import { Outlet } from "react-router";
// components // components
import { AppHeader } from "@/components/core/app-header"; import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper"; import { ContentWrapper } from "@/components/core/content-wrapper";
import { ProjectArchivesHeader } from "../header"; import { ProjectArchivesHeader } from "../header";
export default function ProjectArchiveCyclesLayout({ children }: { children: React.ReactNode }) { export default function ProjectArchiveCyclesLayout() {
return ( return (
<> <>
<AppHeader header={<ProjectArchivesHeader activeTab="cycles" />} /> <AppHeader header={<ProjectArchivesHeader activeTab="cycles" />} />
<ContentWrapper>{children}</ContentWrapper> <ContentWrapper>
<Outlet />
</ContentWrapper>
</> </>
); );
} }

View file

@ -1,21 +1,21 @@
"use client"; "use client";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// components // components
import { PageHead } from "@/components/core/page-title"; import { PageHead } from "@/components/core/page-title";
import { ArchivedCycleLayoutRoot } from "@/components/cycles/archived-cycles"; import { ArchivedCycleLayoutRoot } from "@/components/cycles/archived-cycles";
import { ArchivedCyclesHeader } from "@/components/cycles/archived-cycles/header"; import { ArchivedCyclesHeader } from "@/components/cycles/archived-cycles/header";
// hooks // hooks
import { useProject } from "@/hooks/store/use-project"; import { useProject } from "@/hooks/store/use-project";
import type { Route } from "./+types/page";
const ProjectArchivedCyclesPage = observer(() => { function ProjectArchivedCyclesPage({ params }: Route.ComponentProps) {
// router // router
const { projectId } = useParams(); const { projectId } = params;
// store hooks // store hooks
const { getProjectById } = useProject(); const { getProjectById } = useProject();
// derived values // derived values
const project = projectId ? getProjectById(projectId.toString()) : undefined; const project = getProjectById(projectId);
const pageTitle = project?.name && `${project?.name} - Archived cycles`; const pageTitle = project?.name && `${project?.name} - Archived cycles`;
return ( return (
@ -27,6 +27,6 @@ const ProjectArchivedCyclesPage = observer(() => {
</div> </div>
</> </>
); );
}); }
export default ProjectArchivedCyclesPage; export default observer(ProjectArchivedCyclesPage);

View file

@ -1,7 +1,7 @@
"use client"; "use client";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useParams, useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import useSWR from "swr"; import useSWR from "swr";
// ui // ui
import { Banner } from "@plane/propel/banner"; import { Banner } from "@plane/propel/banner";
@ -15,10 +15,11 @@ import { IssueDetailRoot } from "@/components/issues/issue-detail";
// hooks // hooks
import { useIssueDetail } from "@/hooks/store/use-issue-detail"; import { useIssueDetail } from "@/hooks/store/use-issue-detail";
import { useProject } from "@/hooks/store/use-project"; import { useProject } from "@/hooks/store/use-project";
import type { Route } from "./+types/page";
const ArchivedIssueDetailsPage = observer(() => { function ArchivedIssueDetailsPage({ params }: Route.ComponentProps) {
// router // router
const { workspaceSlug, projectId, archivedIssueId } = useParams(); const { workspaceSlug, projectId, archivedIssueId } = params;
const router = useRouter(); const router = useRouter();
// states // states
// hooks // hooks
@ -29,17 +30,12 @@ const ArchivedIssueDetailsPage = observer(() => {
const { getProjectById } = useProject(); const { getProjectById } = useProject();
const { isLoading } = useSWR( const { isLoading } = useSWR(`ARCHIVED_ISSUE_DETAIL_${workspaceSlug}_${projectId}_${archivedIssueId}`, () =>
workspaceSlug && projectId && archivedIssueId fetchIssue(workspaceSlug, projectId, archivedIssueId)
? `ARCHIVED_ISSUE_DETAIL_${workspaceSlug}_${projectId}_${archivedIssueId}`
: null,
workspaceSlug && projectId && archivedIssueId
? () => fetchIssue(workspaceSlug.toString(), projectId.toString(), archivedIssueId.toString())
: null
); );
// derived values // derived values
const issue = archivedIssueId ? getIssueById(archivedIssueId.toString()) : undefined; const issue = getIssueById(archivedIssueId);
const project = issue ? getProjectById(issue?.project_id ?? "") : undefined; const project = issue ? getProjectById(issue?.project_id ?? "") : undefined;
const pageTitle = project && issue ? `${project?.identifier}-${issue?.sequence_id} ${issue?.name}` : undefined; const pageTitle = project && issue ? `${project?.identifier}-${issue?.sequence_id} ${issue?.name}` : undefined;
@ -84,20 +80,18 @@ const ArchivedIssueDetailsPage = observer(() => {
/> />
<div className="flex h-full overflow-hidden"> <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"> <div className="h-full w-full space-y-3 divide-y-2 divide-custom-border-200 overflow-y-auto">
{workspaceSlug && projectId && archivedIssueId && (
<IssueDetailRoot <IssueDetailRoot
workspaceSlug={workspaceSlug.toString()} workspaceSlug={workspaceSlug}
projectId={projectId.toString()} projectId={projectId}
issueId={archivedIssueId.toString()} issueId={archivedIssueId}
is_archived is_archived
/> />
)}
</div> </div>
</div> </div>
</> </>
)} )}
</> </>
); );
}); }
export default ArchivedIssueDetailsPage; export default observer(ArchivedIssueDetailsPage);

View file

@ -1,15 +1,18 @@
"use client"; "use client";
import { Outlet } from "react-router";
// components // components
import { AppHeader } from "@/components/core/app-header"; import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper"; import { ContentWrapper } from "@/components/core/content-wrapper";
import { ProjectArchivedIssueDetailsHeader } from "./header"; import { ProjectArchivedIssueDetailsHeader } from "./header";
export default function ProjectArchivedIssueDetailLayout({ children }: { children: React.ReactNode }) { export default function ProjectArchivedIssueDetailLayout() {
return ( return (
<> <>
<AppHeader header={<ProjectArchivedIssueDetailsHeader />} /> <AppHeader header={<ProjectArchivedIssueDetailsHeader />} />
<ContentWrapper>{children}</ContentWrapper> <ContentWrapper>
<Outlet />
</ContentWrapper>
</> </>
); );
} }

View file

@ -1,15 +1,18 @@
"use client"; "use client";
import { Outlet } from "react-router";
// components // components
import { AppHeader } from "@/components/core/app-header"; import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper"; import { ContentWrapper } from "@/components/core/content-wrapper";
import { ProjectArchivesHeader } from "../../header"; import { ProjectArchivesHeader } from "../../header";
export default function ProjectArchiveIssuesLayout({ children }: { children: React.ReactNode }) { export default function ProjectArchiveIssuesLayout() {
return ( return (
<> <>
<AppHeader header={<ProjectArchivesHeader activeTab="issues" />} /> <AppHeader header={<ProjectArchivesHeader activeTab="issues" />} />
<ContentWrapper>{children}</ContentWrapper> <ContentWrapper>
<Outlet />
</ContentWrapper>
</> </>
); );
} }

View file

@ -1,21 +1,21 @@
"use client"; "use client";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// components // components
import { PageHead } from "@/components/core/page-title"; import { PageHead } from "@/components/core/page-title";
import { ArchivedIssuesHeader } from "@/components/issues/archived-issues-header"; import { ArchivedIssuesHeader } from "@/components/issues/archived-issues-header";
import { ArchivedIssueLayoutRoot } from "@/components/issues/issue-layouts/roots/archived-issue-layout-root"; import { ArchivedIssueLayoutRoot } from "@/components/issues/issue-layouts/roots/archived-issue-layout-root";
// hooks // hooks
import { useProject } from "@/hooks/store/use-project"; import { useProject } from "@/hooks/store/use-project";
import type { Route } from "./+types/page";
const ProjectArchivedIssuesPage = observer(() => { function ProjectArchivedIssuesPage({ params }: Route.ComponentProps) {
// router // router
const { projectId } = useParams(); const { projectId } = params;
// store hooks // store hooks
const { getProjectById } = useProject(); const { getProjectById } = useProject();
// derived values // derived values
const project = projectId ? getProjectById(projectId.toString()) : undefined; const project = getProjectById(projectId);
const pageTitle = project?.name && `${project?.name} - Archived work items`; const pageTitle = project?.name && `${project?.name} - Archived work items`;
return ( return (
@ -27,6 +27,6 @@ const ProjectArchivedIssuesPage = observer(() => {
</div> </div>
</> </>
); );
}); }
export default ProjectArchivedIssuesPage; export default observer(ProjectArchivedIssuesPage);

View file

@ -1,15 +1,18 @@
"use client"; "use client";
import { Outlet } from "react-router";
// components // components
import { AppHeader } from "@/components/core/app-header"; import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper"; import { ContentWrapper } from "@/components/core/content-wrapper";
import { ProjectArchivesHeader } from "../header"; import { ProjectArchivesHeader } from "../header";
export default function ProjectArchiveModulesLayout({ children }: { children: React.ReactNode }) { export default function ProjectArchiveModulesLayout() {
return ( return (
<> <>
<AppHeader header={<ProjectArchivesHeader activeTab="modules" />} /> <AppHeader header={<ProjectArchivesHeader activeTab="modules" />} />
<ContentWrapper>{children}</ContentWrapper> <ContentWrapper>
<Outlet />
</ContentWrapper>
</> </>
); );
} }

View file

@ -1,20 +1,20 @@
"use client"; "use client";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// components // components
import { PageHead } from "@/components/core/page-title"; import { PageHead } from "@/components/core/page-title";
import { ArchivedModuleLayoutRoot, ArchivedModulesHeader } from "@/components/modules"; import { ArchivedModuleLayoutRoot, ArchivedModulesHeader } from "@/components/modules";
// hooks // hooks
import { useProject } from "@/hooks/store/use-project"; import { useProject } from "@/hooks/store/use-project";
import type { Route } from "./+types/page";
const ProjectArchivedModulesPage = observer(() => { function ProjectArchivedModulesPage({ params }: Route.ComponentProps) {
// router // router
const { projectId } = useParams(); const { projectId } = params;
// store hooks // store hooks
const { getProjectById } = useProject(); const { getProjectById } = useProject();
// derived values // derived values
const project = projectId ? getProjectById(projectId.toString()) : undefined; const project = getProjectById(projectId);
const pageTitle = project?.name && `${project?.name} - Archived modules`; const pageTitle = project?.name && `${project?.name} - Archived modules`;
return ( return (
@ -26,6 +26,6 @@ const ProjectArchivedModulesPage = observer(() => {
</div> </div>
</> </>
); );
}); }
export default ProjectArchivedModulesPage; export default observer(ProjectArchivedModulesPage);

View file

@ -1,9 +1,10 @@
"use client"; "use client";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// plane imports // plane imports
import { cn } from "@plane/utils"; import { cn } from "@plane/utils";
// assets
import emptyCycle from "@/app/assets/empty-state/cycle.svg?url";
// components // components
import { EmptyState } from "@/components/common/empty-state"; import { EmptyState } from "@/components/common/empty-state";
import { PageHead } from "@/components/core/page-title"; import { PageHead } from "@/components/core/page-title";
@ -15,13 +16,12 @@ import { useCycle } from "@/hooks/store/use-cycle";
import { useProject } from "@/hooks/store/use-project"; import { useProject } from "@/hooks/store/use-project";
import { useAppRouter } from "@/hooks/use-app-router"; import { useAppRouter } from "@/hooks/use-app-router";
import useLocalStorage from "@/hooks/use-local-storage"; import useLocalStorage from "@/hooks/use-local-storage";
// assets import type { Route } from "./+types/page";
import emptyCycle from "@/public/empty-state/cycle.svg";
const CycleDetailPage = observer(() => { function CycleDetailPage({ params }: Route.ComponentProps) {
// router // router
const router = useAppRouter(); const router = useAppRouter();
const { workspaceSlug, projectId, cycleId } = useParams(); const { workspaceSlug, projectId, cycleId } = params;
// store hooks // store hooks
const { getCycleById, loader } = useCycle(); const { getCycleById, loader } = useCycle();
const { getProjectById } = useProject(); const { getProjectById } = useProject();
@ -30,14 +30,14 @@ const CycleDetailPage = observer(() => {
const { setValue, storedValue } = useLocalStorage("cycle_sidebar_collapsed", false); const { setValue, storedValue } = useLocalStorage("cycle_sidebar_collapsed", false);
useCyclesDetails({ useCyclesDetails({
workspaceSlug: workspaceSlug?.toString(), workspaceSlug,
projectId: projectId.toString(), projectId,
cycleId: cycleId.toString(), cycleId,
}); });
// derived values // derived values
const isSidebarCollapsed = storedValue ? (storedValue === true ? true : false) : false; const isSidebarCollapsed = storedValue ? (storedValue === true ? true : false) : false;
const cycle = cycleId ? getCycleById(cycleId.toString()) : undefined; const cycle = getCycleById(cycleId);
const project = projectId ? getProjectById(projectId.toString()) : undefined; const project = getProjectById(projectId);
const pageTitle = project?.name && cycle?.name ? `${project?.name} - ${cycle?.name}` : undefined; const pageTitle = project?.name && cycle?.name ? `${project?.name} - ${cycle?.name}` : undefined;
/** /**
@ -46,7 +46,6 @@ const CycleDetailPage = observer(() => {
const toggleSidebar = () => setValue(!isSidebarCollapsed); const toggleSidebar = () => setValue(!isSidebarCollapsed);
// const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout; // const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout;
return ( return (
<> <>
<PageHead title={pageTitle} /> <PageHead title={pageTitle} />
@ -66,7 +65,7 @@ const CycleDetailPage = observer(() => {
<div className="h-full w-full overflow-hidden"> <div className="h-full w-full overflow-hidden">
<CycleLayoutRoot /> <CycleLayoutRoot />
</div> </div>
{cycleId && !isSidebarCollapsed && ( {!isSidebarCollapsed && (
<div <div
className={cn( className={cn(
"flex h-full w-[21.5rem] flex-shrink-0 flex-col gap-3.5 overflow-y-auto border-l border-custom-border-100 bg-custom-sidebar-background-100 px-4 duration-300 vertical-scrollbar scrollbar-sm absolute right-0 z-[13]" "flex h-full w-[21.5rem] flex-shrink-0 flex-col gap-3.5 overflow-y-auto border-l border-custom-border-100 bg-custom-sidebar-background-100 px-4 duration-300 vertical-scrollbar scrollbar-sm absolute right-0 z-[13]"
@ -78,9 +77,9 @@ const CycleDetailPage = observer(() => {
> >
<CycleDetailsSidebar <CycleDetailsSidebar
handleClose={toggleSidebar} handleClose={toggleSidebar}
cycleId={cycleId.toString()} cycleId={cycleId}
projectId={projectId.toString()} projectId={projectId}
workspaceSlug={workspaceSlug.toString()} workspaceSlug={workspaceSlug}
/> />
</div> </div>
)} )}
@ -89,6 +88,6 @@ const CycleDetailPage = observer(() => {
)} )}
</> </>
); );
}); }
export default CycleDetailPage; export default observer(CycleDetailPage);

View file

@ -1,16 +1,19 @@
"use client"; "use client";
import { Outlet } from "react-router";
// components // components
import { AppHeader } from "@/components/core/app-header"; import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper"; import { ContentWrapper } from "@/components/core/content-wrapper";
import { CycleIssuesHeader } from "./header"; import { CycleIssuesHeader } from "./header";
import { CycleIssuesMobileHeader } from "./mobile-header"; import { CycleIssuesMobileHeader } from "./mobile-header";
export default function ProjectCycleIssuesLayout({ children }: { children: React.ReactNode }) { export default function ProjectCycleIssuesLayout() {
return ( return (
<> <>
<AppHeader header={<CycleIssuesHeader />} mobileHeader={<CycleIssuesMobileHeader />} /> <AppHeader header={<CycleIssuesHeader />} mobileHeader={<CycleIssuesMobileHeader />} />
<ContentWrapper>{children}</ContentWrapper> <ContentWrapper>
<Outlet />
</ContentWrapper>
</> </>
); );
} }

View file

@ -1,16 +1,19 @@
"use client"; "use client";
import { Outlet } from "react-router";
// components // components
import { AppHeader } from "@/components/core/app-header"; import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper"; import { ContentWrapper } from "@/components/core/content-wrapper";
import { CyclesListHeader } from "./header"; import { CyclesListHeader } from "./header";
import { CyclesListMobileHeader } from "./mobile-header"; import { CyclesListMobileHeader } from "./mobile-header";
export default function ProjectCyclesListLayout({ children }: { children: React.ReactNode }) { export default function ProjectCyclesListLayout() {
return ( return (
<> <>
<AppHeader header={<CyclesListHeader />} mobileHeader={<CyclesListMobileHeader />} /> <AppHeader header={<CyclesListHeader />} mobileHeader={<CyclesListMobileHeader />} />
<ContentWrapper>{children}</ContentWrapper> <ContentWrapper>
<Outlet />
</ContentWrapper>
</> </>
); );
} }

View file

@ -2,8 +2,8 @@
import { useState } from "react"; import { useState } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// plane imports // plane imports
import { useTheme } from "next-themes";
import { EUserPermissionsLevel, CYCLE_TRACKER_ELEMENTS } from "@plane/constants"; import { EUserPermissionsLevel, CYCLE_TRACKER_ELEMENTS } from "@plane/constants";
import { useTranslation } from "@plane/i18n"; import { useTranslation } from "@plane/i18n";
import { EmptyStateDetailed } from "@plane/propel/empty-state"; import { EmptyStateDetailed } from "@plane/propel/empty-state";
@ -12,6 +12,10 @@ import { EUserProjectRoles } from "@plane/types";
// components // components
import { Header, EHeaderVariant } from "@plane/ui"; import { Header, EHeaderVariant } from "@plane/ui";
import { calculateTotalFilters } from "@plane/utils"; import { calculateTotalFilters } from "@plane/utils";
// assets
import darkEmptyState from "@/app/assets/empty-state/disabled-feature/cycles-dark.webp?url";
import lightEmptyState from "@/app/assets/empty-state/disabled-feature/cycles-light.webp?url";
// components
import { PageHead } from "@/components/core/page-title"; import { PageHead } from "@/components/core/page-title";
import { CycleAppliedFiltersList } from "@/components/cycles/applied-filters"; import { CycleAppliedFiltersList } from "@/components/cycles/applied-filters";
import { CyclesView } from "@/components/cycles/cycles-view"; import { CyclesView } from "@/components/cycles/cycles-view";
@ -24,9 +28,9 @@ import { useCycleFilter } from "@/hooks/store/use-cycle-filter";
import { useProject } from "@/hooks/store/use-project"; import { useProject } from "@/hooks/store/use-project";
import { useUserPermissions } from "@/hooks/store/user"; import { useUserPermissions } from "@/hooks/store/user";
import { useAppRouter } from "@/hooks/use-app-router"; import { useAppRouter } from "@/hooks/use-app-router";
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; import type { Route } from "./+types/page";
const ProjectCyclesPage = observer(() => { function ProjectCyclesPage({ params }: Route.ComponentProps) {
// states // states
const [createModal, setCreateModal] = useState(false); const [createModal, setCreateModal] = useState(false);
// store hooks // store hooks
@ -34,35 +38,34 @@ const ProjectCyclesPage = observer(() => {
const { getProjectById, currentProjectDetails } = useProject(); const { getProjectById, currentProjectDetails } = useProject();
// router // router
const router = useAppRouter(); const router = useAppRouter();
const { workspaceSlug, projectId } = useParams(); const { workspaceSlug, projectId } = params;
// theme hook
const { resolvedTheme } = useTheme();
// plane hooks // plane hooks
const { t } = useTranslation(); const { t } = useTranslation();
// cycle filters hook // cycle filters hook
const { clearAllFilters, currentProjectFilters, updateFilters } = useCycleFilter(); const { clearAllFilters, currentProjectFilters, updateFilters } = useCycleFilter();
const { allowPermissions } = useUserPermissions(); const { allowPermissions } = useUserPermissions();
// derived values // derived values
const resolvedEmptyState = resolvedTheme === "light" ? lightEmptyState : darkEmptyState;
const totalCycles = currentProjectCycleIds?.length ?? 0; const totalCycles = currentProjectCycleIds?.length ?? 0;
const project = projectId ? getProjectById(projectId?.toString()) : undefined; const project = getProjectById(projectId);
const pageTitle = project?.name ? `${project?.name} - ${t("common.cycles", { count: 2 })}` : undefined; const pageTitle = project?.name ? `${project?.name} - ${t("common.cycles", { count: 2 })}` : undefined;
const hasAdminLevelPermission = allowPermissions([EUserProjectRoles.ADMIN], EUserPermissionsLevel.PROJECT); const hasAdminLevelPermission = allowPermissions([EUserProjectRoles.ADMIN], EUserPermissionsLevel.PROJECT);
const hasMemberLevelPermission = allowPermissions( const hasMemberLevelPermission = allowPermissions(
[EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER], [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER],
EUserPermissionsLevel.PROJECT EUserPermissionsLevel.PROJECT
); );
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/disabled-feature/cycles" });
const handleRemoveFilter = (key: keyof TCycleFilters, value: string | null) => { const handleRemoveFilter = (key: keyof TCycleFilters, value: string | null) => {
if (!projectId) return;
let newValues = currentProjectFilters?.[key] ?? []; let newValues = currentProjectFilters?.[key] ?? [];
if (!value) newValues = []; if (!value) newValues = [];
else newValues = newValues.filter((val) => val !== value); else newValues = newValues.filter((val) => val !== value);
updateFilters(projectId.toString(), { [key]: newValues }); updateFilters(projectId, { [key]: newValues });
}; };
if (!workspaceSlug || !projectId) return <></>;
// No access to cycle // No access to cycle
if (currentProjectDetails?.cycle_view === false) if (currentProjectDetails?.cycle_view === false)
return ( return (
@ -70,7 +73,7 @@ const ProjectCyclesPage = observer(() => {
<DetailedEmptyState <DetailedEmptyState
title={t("disabled_project.empty_state.cycle.title")} title={t("disabled_project.empty_state.cycle.title")}
description={t("disabled_project.empty_state.cycle.description")} description={t("disabled_project.empty_state.cycle.description")}
assetPath={resolvedPath} assetPath={resolvedEmptyState}
primaryButton={{ primaryButton={{
text: t("disabled_project.empty_state.cycle.primary_button.text"), text: t("disabled_project.empty_state.cycle.primary_button.text"),
onClick: () => { onClick: () => {
@ -89,8 +92,8 @@ const ProjectCyclesPage = observer(() => {
<PageHead title={pageTitle} /> <PageHead title={pageTitle} />
<div className="w-full h-full"> <div className="w-full h-full">
<CycleCreateUpdateModal <CycleCreateUpdateModal
workspaceSlug={workspaceSlug.toString()} workspaceSlug={workspaceSlug}
projectId={projectId.toString()} projectId={projectId}
isOpen={createModal} isOpen={createModal}
handleClose={() => setCreateModal(false)} handleClose={() => setCreateModal(false)}
/> />
@ -117,18 +120,18 @@ const ProjectCyclesPage = observer(() => {
<Header variant={EHeaderVariant.TERNARY}> <Header variant={EHeaderVariant.TERNARY}>
<CycleAppliedFiltersList <CycleAppliedFiltersList
appliedFilters={currentProjectFilters ?? {}} appliedFilters={currentProjectFilters ?? {}}
handleClearAllFilters={() => clearAllFilters(projectId.toString())} handleClearAllFilters={() => clearAllFilters(projectId)}
handleRemoveFilter={handleRemoveFilter} handleRemoveFilter={handleRemoveFilter}
/> />
</Header> </Header>
)} )}
<CyclesView workspaceSlug={workspaceSlug.toString()} projectId={projectId.toString()} /> <CyclesView workspaceSlug={workspaceSlug} projectId={projectId} />
</> </>
)} )}
</div> </div>
</> </>
); );
}); }
export default ProjectCyclesPage; export default observer(ProjectCyclesPage);

View file

@ -1,15 +1,18 @@
"use client"; "use client";
import { Outlet } from "react-router";
// components // components
import { AppHeader } from "@/components/core/app-header"; import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper"; import { ContentWrapper } from "@/components/core/content-wrapper";
import { ProjectInboxHeader } from "@/plane-web/components/projects/settings/intake/header"; import { ProjectInboxHeader } from "@/plane-web/components/projects/settings/intake/header";
export default function ProjectInboxIssuesLayout({ children }: { children: React.ReactNode }) { export default function ProjectInboxIssuesLayout() {
return ( return (
<> <>
<AppHeader header={<ProjectInboxHeader />} /> <AppHeader header={<ProjectInboxHeader />} />
<ContentWrapper>{children}</ContentWrapper> <ContentWrapper>
<Outlet />
</ContentWrapper>
</> </>
); );
} }

View file

@ -1,10 +1,14 @@
"use client"; "use client";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useParams, useSearchParams } from "next/navigation"; import { useSearchParams } from "next/navigation";
import { useTheme } from "next-themes";
// plane imports // plane imports
import { EUserPermissionsLevel } from "@plane/constants"; import { EUserPermissionsLevel } from "@plane/constants";
import { useTranslation } from "@plane/i18n"; import { useTranslation } from "@plane/i18n";
import { EUserProjectRoles, EInboxIssueCurrentTab } from "@plane/types"; import { EUserProjectRoles, EInboxIssueCurrentTab } from "@plane/types";
// assets
import darkIntakeAsset from "@/app/assets/empty-state/disabled-feature/intake-dark.webp?url";
import lightIntakeAsset from "@/app/assets/empty-state/disabled-feature/intake-light.webp?url";
// components // components
import { PageHead } from "@/components/core/page-title"; import { PageHead } from "@/components/core/page-title";
import { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root"; import { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root";
@ -13,15 +17,17 @@ import { InboxIssueRoot } from "@/components/inbox";
import { useProject } from "@/hooks/store/use-project"; import { useProject } from "@/hooks/store/use-project";
import { useUserPermissions } from "@/hooks/store/user"; import { useUserPermissions } from "@/hooks/store/user";
import { useAppRouter } from "@/hooks/use-app-router"; import { useAppRouter } from "@/hooks/use-app-router";
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; import type { Route } from "./+types/page";
const ProjectInboxPage = observer(() => { function ProjectInboxPage({ params }: Route.ComponentProps) {
/// router /// router
const router = useAppRouter(); const router = useAppRouter();
const { workspaceSlug, projectId } = useParams(); const { workspaceSlug, projectId } = params;
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const navigationTab = searchParams.get("currentTab"); const navigationTab = searchParams.get("currentTab");
const inboxIssueId = searchParams.get("inboxIssueId"); const inboxIssueId = searchParams.get("inboxIssueId");
// theme hook
const { resolvedTheme } = useTheme();
// plane hooks // plane hooks
const { t } = useTranslation(); const { t } = useTranslation();
// hooks // hooks
@ -29,7 +35,7 @@ const ProjectInboxPage = observer(() => {
const { allowPermissions } = useUserPermissions(); const { allowPermissions } = useUserPermissions();
// derived values // derived values
const canPerformEmptyStateActions = allowPermissions([EUserProjectRoles.ADMIN], EUserPermissionsLevel.PROJECT); const canPerformEmptyStateActions = allowPermissions([EUserProjectRoles.ADMIN], EUserPermissionsLevel.PROJECT);
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/disabled-feature/intake" }); const resolvedPath = resolvedTheme === "light" ? lightIntakeAsset : darkIntakeAsset;
// No access to inbox // No access to inbox
if (currentProjectDetails?.inbox_view === false) if (currentProjectDetails?.inbox_view === false)
@ -65,22 +71,20 @@ const ProjectInboxPage = observer(() => {
: EInboxIssueCurrentTab.CLOSED : EInboxIssueCurrentTab.CLOSED
: undefined; : undefined;
if (!workspaceSlug || !projectId) return <></>;
return ( return (
<div className="flex h-full flex-col"> <div className="flex h-full flex-col">
<PageHead title={pageTitle} /> <PageHead title={pageTitle} />
<div className="w-full h-full overflow-hidden"> <div className="w-full h-full overflow-hidden">
<InboxIssueRoot <InboxIssueRoot
workspaceSlug={workspaceSlug.toString()} workspaceSlug={workspaceSlug}
projectId={projectId.toString()} projectId={projectId}
inboxIssueId={inboxIssueId?.toString() || undefined} inboxIssueId={inboxIssueId || undefined}
inboxAccessible={currentProjectDetails?.inbox_view || false} inboxAccessible={currentProjectDetails?.inbox_view || false}
navigationTab={currentNavigationTab} navigationTab={currentNavigationTab}
/> />
</div> </div>
</div> </div>
); );
}); }
export default ProjectInboxPage; export default observer(ProjectInboxPage);

View file

@ -1,64 +1,68 @@
"use client"; "use client";
import { useEffect } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import { useTheme } from "next-themes"; import { useTheme } from "next-themes";
import useSWR from "swr"; import { redirect } from "react-router";
import { useTranslation } from "@plane/i18n"; import { useTranslation } from "@plane/i18n";
// assets
import emptyIssueDark from "@/app/assets/empty-state/search/issues-dark.webp?url";
import emptyIssueLight from "@/app/assets/empty-state/search/issues-light.webp?url";
// components // components
import { EmptyState } from "@/components/common/empty-state"; import { EmptyState } from "@/components/common/empty-state";
import { LogoSpinner } from "@/components/common/logo-spinner"; import { LogoSpinner } from "@/components/common/logo-spinner";
// hooks // hooks
import { useAppRouter } from "@/hooks/use-app-router"; 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";
// services // services
import { IssueService } from "@/services/issue/issue.service"; import { IssueService } from "@/services/issue/issue.service";
// types
import type { Route } from "./+types/page";
const issueService = new IssueService(); const issueService = new IssueService();
const IssueDetailsPage = observer(() => { export async function clientLoader({ params }: Route.ClientLoaderArgs) {
const { workspaceSlug, projectId, issueId } = params;
try {
const data = await issueService.getIssueMetaFromURL(workspaceSlug, projectId, issueId);
if (data) {
throw redirect(`/${workspaceSlug}/browse/${data.project_identifier}-${data.sequence_id}`);
}
return { error: true, workspaceSlug };
} catch (error) {
// If it's a redirect, rethrow it
if (error instanceof Response) {
throw error;
}
// Otherwise return error state
return { error: true, workspaceSlug };
}
}
export default function IssueDetailsPage({ loaderData }: Route.ComponentProps) {
const router = useAppRouter(); const router = useAppRouter();
const { t } = useTranslation(); const { t } = useTranslation();
const { workspaceSlug, projectId, issueId } = useParams();
const { resolvedTheme } = useTheme(); const { resolvedTheme } = useTheme();
const { data, isLoading, error } = useSWR( if (loaderData.error) {
workspaceSlug && projectId && issueId ? `ISSUE_DETAIL_META_${workspaceSlug}_${projectId}_${issueId}` : null,
workspaceSlug && projectId && issueId
? () => issueService.getIssueMetaFromURL(workspaceSlug.toString(), projectId.toString(), issueId.toString())
: null
);
useEffect(() => {
if (data) {
router.push(`/${workspaceSlug}/browse/${data.project_identifier}-${data.sequence_id}`);
}
}, [workspaceSlug, data]);
return ( return (
<div className="flex items-center justify-center size-full"> <div className="flex items-center justify-center size-full">
{error ? (
<EmptyState <EmptyState
image={resolvedTheme === "dark" ? emptyIssueDark : emptyIssueLight} image={resolvedTheme === "dark" ? emptyIssueDark : emptyIssueLight}
title={t("issue.empty_state.issue_detail.title")} title={t("issue.empty_state.issue_detail.title")}
description={t("issue.empty_state.issue_detail.description")} description={t("issue.empty_state.issue_detail.description")}
primaryButton={{ primaryButton={{
text: t("issue.empty_state.issue_detail.primary_button.text"), text: t("issue.empty_state.issue_detail.primary_button.text"),
onClick: () => router.push(`/${workspaceSlug}/workspace-views/all-issues/`), onClick: () => router.push(`/${loaderData.workspaceSlug}/workspace-views/all-issues/`),
}} }}
/> />
) : isLoading ? (
<>
<LogoSpinner />
</>
) : (
<></>
)}
</div> </div>
); );
}); }
export default IssueDetailsPage; return (
<div className="flex items-center justify-center size-full">
<LogoSpinner />
</div>
);
}

View file

@ -1,16 +1,19 @@
"use client"; "use client";
// components // components
import { Outlet } from "react-router";
import { AppHeader } from "@/components/core/app-header"; import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper"; import { ContentWrapper } from "@/components/core/content-wrapper";
import { ProjectIssuesHeader } from "./header"; import { ProjectIssuesHeader } from "./header";
import { ProjectIssuesMobileHeader } from "./mobile-header"; import { ProjectIssuesMobileHeader } from "./mobile-header";
export default function ProjectIssuesLayout({ children }: { children: React.ReactNode }) { export default function ProjectIssuesLayout() {
return ( return (
<> <>
<AppHeader header={<ProjectIssuesHeader />} mobileHeader={<ProjectIssuesMobileHeader />} /> <AppHeader header={<ProjectIssuesHeader />} mobileHeader={<ProjectIssuesMobileHeader />} />
<ContentWrapper>{children}</ContentWrapper> <ContentWrapper>
<Outlet />
</ContentWrapper>
</> </>
); );
} }

View file

@ -1,8 +1,6 @@
"use client"; "use client";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import Head from "next/head";
import { useParams } from "next/navigation";
// i18n // i18n
import { useTranslation } from "@plane/i18n"; import { useTranslation } from "@plane/i18n";
// components // components
@ -10,35 +8,27 @@ import { PageHead } from "@/components/core/page-title";
import { ProjectLayoutRoot } from "@/components/issues/issue-layouts/roots/project-layout-root"; import { ProjectLayoutRoot } from "@/components/issues/issue-layouts/roots/project-layout-root";
// hooks // hooks
import { useProject } from "@/hooks/store/use-project"; import { useProject } from "@/hooks/store/use-project";
import type { Route } from "./+types/page";
const ProjectIssuesPage = observer(() => { function ProjectIssuesPage({ params }: Route.ComponentProps) {
const { projectId } = useParams(); const { projectId } = params;
// i18n // i18n
const { t } = useTranslation(); const { t } = useTranslation();
// store // store
const { getProjectById } = useProject(); const { getProjectById } = useProject();
if (!projectId) {
return <></>;
}
// derived values // derived values
const project = getProjectById(projectId.toString()); const project = getProjectById(projectId);
const pageTitle = project?.name ? `${project?.name} - ${t("issue.label", { count: 2 })}` : undefined; // Count is for pluralization const pageTitle = project?.name ? `${project?.name} - ${t("issue.label", { count: 2 })}` : undefined; // Count is for pluralization
return ( return (
<> <>
<PageHead title={pageTitle} /> <PageHead title={pageTitle} />
<Head>
<title>
{project?.name} - {t("issue.label", { count: 2 })}
</title>
</Head>
<div className="h-full w-full"> <div className="h-full w-full">
<ProjectLayoutRoot /> <ProjectLayoutRoot />
</div> </div>
</> </>
); );
}); }
export default ProjectIssuesPage; export default observer(ProjectIssuesPage);

View file

@ -1,27 +1,27 @@
"use client"; "use client";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import useSWR from "swr"; import useSWR from "swr";
// components // plane imports
import { cn } from "@plane/utils"; import { cn } from "@plane/utils";
// assets
import emptyModule from "@/app/assets/empty-state/module.svg?url";
// components
import { EmptyState } from "@/components/common/empty-state"; import { EmptyState } from "@/components/common/empty-state";
import { PageHead } from "@/components/core/page-title"; import { PageHead } from "@/components/core/page-title";
import { ModuleLayoutRoot } from "@/components/issues/issue-layouts/roots/module-layout-root"; import { ModuleLayoutRoot } from "@/components/issues/issue-layouts/roots/module-layout-root";
import { ModuleAnalyticsSidebar } from "@/components/modules"; import { ModuleAnalyticsSidebar } from "@/components/modules";
// helpers
// hooks // hooks
import { useModule } from "@/hooks/store/use-module"; import { useModule } from "@/hooks/store/use-module";
import { useProject } from "@/hooks/store/use-project"; import { useProject } from "@/hooks/store/use-project";
import { useAppRouter } from "@/hooks/use-app-router"; import { useAppRouter } from "@/hooks/use-app-router";
import useLocalStorage from "@/hooks/use-local-storage"; import useLocalStorage from "@/hooks/use-local-storage";
// assets import type { Route } from "./+types/page";
import emptyModule from "@/public/empty-state/module.svg";
const ModuleIssuesPage = observer(() => { function ModuleIssuesPage({ params }: Route.ComponentProps) {
// router // router
const router = useAppRouter(); const router = useAppRouter();
const { workspaceSlug, projectId, moduleId } = useParams(); const { workspaceSlug, projectId, moduleId } = params;
// store hooks // store hooks
const { fetchModuleDetails, getModuleById } = useModule(); const { fetchModuleDetails, getModuleById } = useModule();
const { getProjectById } = useProject(); const { getProjectById } = useProject();
@ -30,25 +30,19 @@ const ModuleIssuesPage = observer(() => {
const { setValue, storedValue } = useLocalStorage("module_sidebar_collapsed", "false"); const { setValue, storedValue } = useLocalStorage("module_sidebar_collapsed", "false");
const isSidebarCollapsed = storedValue ? (storedValue === "true" ? true : false) : false; const isSidebarCollapsed = storedValue ? (storedValue === "true" ? true : false) : false;
// fetching module details // fetching module details
const { error } = useSWR( const { error } = useSWR(`CURRENT_MODULE_DETAILS_${moduleId}`, () =>
workspaceSlug && projectId && moduleId ? `CURRENT_MODULE_DETAILS_${moduleId.toString()}` : null, fetchModuleDetails(workspaceSlug, projectId, moduleId)
workspaceSlug && projectId && moduleId
? () => fetchModuleDetails(workspaceSlug.toString(), projectId.toString(), moduleId.toString())
: null
); );
// derived values // derived values
const projectModule = moduleId ? getModuleById(moduleId.toString()) : undefined; const projectModule = getModuleById(moduleId);
const project = projectId ? getProjectById(projectId.toString()) : undefined; const project = getProjectById(projectId);
const pageTitle = project?.name && projectModule?.name ? `${project?.name} - ${projectModule?.name}` : undefined; const pageTitle = project?.name && projectModule?.name ? `${project?.name} - ${projectModule?.name}` : undefined;
const toggleSidebar = () => { const toggleSidebar = () => {
setValue(`${!isSidebarCollapsed}`); setValue(`${!isSidebarCollapsed}`);
}; };
if (!workspaceSlug || !projectId || !moduleId) return <></>;
// const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout; // const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout;
return ( return (
<> <>
<PageHead title={pageTitle} /> <PageHead title={pageTitle} />
@ -67,7 +61,7 @@ const ModuleIssuesPage = observer(() => {
<div className="h-full w-full overflow-hidden"> <div className="h-full w-full overflow-hidden">
<ModuleLayoutRoot /> <ModuleLayoutRoot />
</div> </div>
{moduleId && !isSidebarCollapsed && ( {!isSidebarCollapsed && (
<div <div
className={cn( className={cn(
"flex h-full w-[24rem] flex-shrink-0 flex-col gap-3.5 overflow-y-auto border-l border-custom-border-100 bg-custom-sidebar-background-100 px-6 duration-300 vertical-scrollbar scrollbar-sm absolute right-0 z-[13]" "flex h-full w-[24rem] flex-shrink-0 flex-col gap-3.5 overflow-y-auto border-l border-custom-border-100 bg-custom-sidebar-background-100 px-6 duration-300 vertical-scrollbar scrollbar-sm absolute right-0 z-[13]"
@ -77,13 +71,13 @@ const ModuleIssuesPage = observer(() => {
"0px 1px 4px 0px rgba(0, 0, 0, 0.06), 0px 2px 4px 0px rgba(16, 24, 40, 0.06), 0px 1px 8px -1px rgba(16, 24, 40, 0.06)", "0px 1px 4px 0px rgba(0, 0, 0, 0.06), 0px 2px 4px 0px rgba(16, 24, 40, 0.06), 0px 1px 8px -1px rgba(16, 24, 40, 0.06)",
}} }}
> >
<ModuleAnalyticsSidebar moduleId={moduleId.toString()} handleClose={toggleSidebar} /> <ModuleAnalyticsSidebar moduleId={moduleId} handleClose={toggleSidebar} />
</div> </div>
)} )}
</div> </div>
)} )}
</> </>
); );
}); }
export default ModuleIssuesPage; export default observer(ModuleIssuesPage);

View file

@ -1,16 +1,19 @@
"use client"; "use client";
import { Outlet } from "react-router";
// components // components
import { AppHeader } from "@/components/core/app-header"; import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper"; import { ContentWrapper } from "@/components/core/content-wrapper";
import { ModuleIssuesHeader } from "./header"; import { ModuleIssuesHeader } from "./header";
import { ModuleIssuesMobileHeader } from "./mobile-header"; import { ModuleIssuesMobileHeader } from "./mobile-header";
export default function ProjectModuleIssuesLayout({ children }: { children: React.ReactNode }) { export default function ProjectModuleIssuesLayout() {
return ( return (
<> <>
<AppHeader header={<ModuleIssuesHeader />} mobileHeader={<ModuleIssuesMobileHeader />} /> <AppHeader header={<ModuleIssuesHeader />} mobileHeader={<ModuleIssuesMobileHeader />} />
<ContentWrapper>{children}</ContentWrapper> <ContentWrapper>
<Outlet />
</ContentWrapper>
</> </>
); );
} }

View file

@ -1,16 +1,19 @@
"use client"; "use client";
import { Outlet } from "react-router";
// components // components
import { AppHeader } from "@/components/core/app-header"; import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper"; import { ContentWrapper } from "@/components/core/content-wrapper";
import { ModulesListHeader } from "./header"; import { ModulesListHeader } from "./header";
import { ModulesListMobileHeader } from "./mobile-header"; import { ModulesListMobileHeader } from "./mobile-header";
export default function ProjectModulesListLayout({ children }: { children: React.ReactNode }) { export default function ProjectModulesListLayout() {
return ( return (
<> <>
<AppHeader header={<ModulesListHeader />} mobileHeader={<ModulesListMobileHeader />} /> <AppHeader header={<ModulesListHeader />} mobileHeader={<ModulesListMobileHeader />} />
<ContentWrapper>{children}</ContentWrapper> <ContentWrapper>
<Outlet />
</ContentWrapper>
</> </>
); );
} }

View file

@ -2,57 +2,63 @@
import { useCallback } from "react"; import { useCallback } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useParams } from "next/navigation"; import { useTheme } from "next-themes";
// types // plane imports
import { EUserPermissionsLevel } from "@plane/constants"; import { EUserPermissionsLevel } from "@plane/constants";
import { useTranslation } from "@plane/i18n"; import { useTranslation } from "@plane/i18n";
import type { TModuleFilters } from "@plane/types"; import type { TModuleFilters } from "@plane/types";
import { EUserProjectRoles } from "@plane/types"; import { EUserProjectRoles } from "@plane/types";
// components
import { calculateTotalFilters } from "@plane/utils"; import { calculateTotalFilters } from "@plane/utils";
// assets
import darkModulesAsset from "@/app/assets/empty-state/disabled-feature/modules-dark.webp?url";
import lightModulesAsset from "@/app/assets/empty-state/disabled-feature/modules-light.webp?url";
// components
import { PageHead } from "@/components/core/page-title"; import { PageHead } from "@/components/core/page-title";
import { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root"; import { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root";
import { ModuleAppliedFiltersList, ModulesListView } from "@/components/modules"; import { ModuleAppliedFiltersList, ModulesListView } from "@/components/modules";
// helpers
// hooks // hooks
import { useModuleFilter } from "@/hooks/store/use-module-filter"; import { useModuleFilter } from "@/hooks/store/use-module-filter";
import { useProject } from "@/hooks/store/use-project"; import { useProject } from "@/hooks/store/use-project";
import { useUserPermissions } from "@/hooks/store/user"; import { useUserPermissions } from "@/hooks/store/user";
import { useAppRouter } from "@/hooks/use-app-router"; import { useAppRouter } from "@/hooks/use-app-router";
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; import type { Route } from "./+types/page";
const ProjectModulesPage = observer(() => { function ProjectModulesPage({ params }: Route.ComponentProps) {
// router // router
const router = useAppRouter(); const router = useAppRouter();
const { workspaceSlug, projectId } = useParams(); const { workspaceSlug, projectId } = params;
// theme hook
const { resolvedTheme } = useTheme();
// plane hooks // plane hooks
const { t } = useTranslation(); const { t } = useTranslation();
// store // store
const { getProjectById, currentProjectDetails } = useProject(); const { getProjectById, currentProjectDetails } = useProject();
const { currentProjectFilters, currentProjectDisplayFilters, clearAllFilters, updateFilters, updateDisplayFilters } = const {
useModuleFilter(); currentProjectFilters = {},
currentProjectDisplayFilters,
clearAllFilters,
updateFilters,
updateDisplayFilters,
} = useModuleFilter();
const { allowPermissions } = useUserPermissions(); const { allowPermissions } = useUserPermissions();
// derived values // derived values
const project = projectId ? getProjectById(projectId.toString()) : undefined; const project = getProjectById(projectId);
const pageTitle = project?.name ? `${project?.name} - Modules` : undefined; const pageTitle = project?.name ? `${project?.name} - Modules` : undefined;
const canPerformEmptyStateActions = allowPermissions([EUserProjectRoles.ADMIN], EUserPermissionsLevel.PROJECT); const canPerformEmptyStateActions = allowPermissions([EUserProjectRoles.ADMIN], EUserPermissionsLevel.PROJECT);
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/disabled-feature/modules" }); const resolvedPath = resolvedTheme === "light" ? lightModulesAsset : darkModulesAsset;
const handleRemoveFilter = useCallback( const handleRemoveFilter = useCallback(
(key: keyof TModuleFilters, value: string | null) => { (key: keyof TModuleFilters, value: string | null) => {
if (!projectId) return; let newValues = currentProjectFilters[key] ?? [];
let newValues = currentProjectFilters?.[key] ?? [];
if (!value) newValues = []; if (!value) newValues = [];
else newValues = newValues.filter((val) => val !== value); else newValues = newValues.filter((val) => val !== value);
updateFilters(projectId.toString(), { [key]: newValues }); updateFilters(projectId, { [key]: newValues });
}, },
[currentProjectFilters, projectId, updateFilters] [currentProjectFilters, projectId, updateFilters]
); );
if (!workspaceSlug || !projectId) return <></>;
// No access to // No access to
if (currentProjectDetails?.module_view === false) if (currentProjectDetails?.module_view === false)
return ( return (
@ -76,16 +82,13 @@ const ProjectModulesPage = observer(() => {
<> <>
<PageHead title={pageTitle} /> <PageHead title={pageTitle} />
<div className="h-full w-full flex flex-col"> <div className="h-full w-full flex flex-col">
{(calculateTotalFilters(currentProjectFilters ?? {}) !== 0 || currentProjectDisplayFilters?.favorites) && ( {(calculateTotalFilters(currentProjectFilters) !== 0 || currentProjectDisplayFilters?.favorites) && (
<ModuleAppliedFiltersList <ModuleAppliedFiltersList
appliedFilters={currentProjectFilters ?? {}} appliedFilters={currentProjectFilters}
isFavoriteFilterApplied={currentProjectDisplayFilters?.favorites ?? false} isFavoriteFilterApplied={currentProjectDisplayFilters?.favorites ?? false}
handleClearAllFilters={() => clearAllFilters(`${projectId}`)} handleClearAllFilters={() => clearAllFilters(projectId)}
handleRemoveFilter={handleRemoveFilter} handleRemoveFilter={handleRemoveFilter}
handleDisplayFiltersUpdate={(val) => { handleDisplayFiltersUpdate={(val) => updateDisplayFilters(projectId, val)}
if (!projectId) return;
updateDisplayFilters(projectId.toString(), val);
}}
alwaysAllowEditing alwaysAllowEditing
/> />
)} )}
@ -93,6 +96,6 @@ const ProjectModulesPage = observer(() => {
</div> </div>
</> </>
); );
}); }
export default ProjectModulesPage; export default observer(ProjectModulesPage);

View file

@ -3,7 +3,6 @@
import { useCallback, useEffect, useMemo } from "react"; import { useCallback, useEffect, useMemo } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import Link from "next/link"; import Link from "next/link";
import { useParams } from "next/navigation";
import useSWR from "swr"; import useSWR from "swr";
// plane types // plane types
import { getButtonStyling } from "@plane/propel/button"; import { getButtonStyling } from "@plane/propel/button";
@ -29,33 +28,34 @@ import { EPageStoreType, usePage, usePageStore } from "@/plane-web/hooks/store";
import { WorkspaceService } from "@/plane-web/services"; import { WorkspaceService } from "@/plane-web/services";
// services // services
import { ProjectPageService, ProjectPageVersionService } from "@/services/page"; import { ProjectPageService, ProjectPageVersionService } from "@/services/page";
import type { Route } from "./+types/page";
const workspaceService = new WorkspaceService(); const workspaceService = new WorkspaceService();
const projectPageService = new ProjectPageService(); const projectPageService = new ProjectPageService();
const projectPageVersionService = new ProjectPageVersionService(); const projectPageVersionService = new ProjectPageVersionService();
const storeType = EPageStoreType.PROJECT; const storeType = EPageStoreType.PROJECT;
const PageDetailsPage = observer(() => { function PageDetailsPage({ params }: Route.ComponentProps) {
// router // router
const router = useAppRouter(); const router = useAppRouter();
const { workspaceSlug, projectId, pageId } = useParams(); const { workspaceSlug, projectId, pageId } = params;
// store hooks // store hooks
const { createPage, fetchPageDetails } = usePageStore(storeType); const { createPage, fetchPageDetails } = usePageStore(storeType);
const page = usePage({ const page = usePage({
pageId: pageId?.toString() ?? "", pageId,
storeType, storeType,
}); });
const { getWorkspaceBySlug } = useWorkspace(); const { getWorkspaceBySlug } = useWorkspace();
const { uploadEditorAsset } = useEditorAsset(); const { uploadEditorAsset } = useEditorAsset();
// derived values // derived values
const workspaceId = workspaceSlug ? (getWorkspaceBySlug(workspaceSlug.toString())?.id ?? "") : ""; const workspaceId = workspaceSlug ? (getWorkspaceBySlug(workspaceSlug)?.id ?? "") : "";
const { canCurrentUserAccessPage, id, name, updateDescription } = page ?? {}; const { canCurrentUserAccessPage, id, name, updateDescription } = page ?? {};
// entity search handler // entity search handler
const fetchEntityCallback = useCallback( const fetchEntityCallback = useCallback(
async (payload: TSearchEntityRequestPayload) => async (payload: TSearchEntityRequestPayload) =>
await workspaceService.searchEntity(workspaceSlug?.toString() ?? "", { await workspaceService.searchEntity(workspaceSlug, {
...payload, ...payload,
project_id: projectId?.toString() ?? "", project_id: projectId,
}), }),
[projectId, workspaceSlug] [projectId, workspaceSlug]
); );
@ -63,10 +63,8 @@ const PageDetailsPage = observer(() => {
const { getEditorFileHandlers } = useEditorConfig(); const { getEditorFileHandlers } = useEditorConfig();
// fetch page details // fetch page details
const { error: pageDetailsError } = useSWR( const { error: pageDetailsError } = useSWR(
workspaceSlug && projectId && pageId ? `PAGE_DETAILS_${pageId}` : null, `PAGE_DETAILS_${pageId}`,
workspaceSlug && projectId && pageId () => fetchPageDetails(workspaceSlug, projectId, pageId),
? () => fetchPageDetails(workspaceSlug?.toString(), projectId?.toString(), pageId.toString())
: null,
{ {
revalidateIfStale: true, revalidateIfStale: true,
revalidateOnFocus: true, revalidateOnFocus: true,
@ -77,33 +75,17 @@ const PageDetailsPage = observer(() => {
const pageRootHandlers: TPageRootHandlers = useMemo( const pageRootHandlers: TPageRootHandlers = useMemo(
() => ({ () => ({
create: createPage, create: createPage,
fetchAllVersions: async (pageId) => { fetchAllVersions: async (pageId) =>
if (!workspaceSlug || !projectId) return; await projectPageVersionService.fetchAllVersions(workspaceSlug, projectId, pageId),
return await projectPageVersionService.fetchAllVersions(workspaceSlug.toString(), projectId.toString(), pageId);
},
fetchDescriptionBinary: async () => { fetchDescriptionBinary: async () => {
if (!workspaceSlug || !projectId || !id) return; if (!id) return;
return await projectPageService.fetchDescriptionBinary(workspaceSlug.toString(), projectId.toString(), id); return await projectPageService.fetchDescriptionBinary(workspaceSlug, projectId, id);
}, },
fetchEntity: fetchEntityCallback, fetchEntity: fetchEntityCallback,
fetchVersionDetails: async (pageId, versionId) => { fetchVersionDetails: async (pageId, versionId) =>
if (!workspaceSlug || !projectId) return; await projectPageVersionService.fetchVersionById(workspaceSlug, projectId, pageId, versionId),
return await projectPageVersionService.fetchVersionById( restoreVersion: async (pageId, versionId) =>
workspaceSlug.toString(), await projectPageVersionService.restoreVersion(workspaceSlug, projectId, pageId, versionId),
projectId.toString(),
pageId,
versionId
);
},
restoreVersion: async (pageId, versionId) => {
if (!workspaceSlug || !projectId) return;
await projectPageVersionService.restoreVersion(
workspaceSlug.toString(),
projectId.toString(),
pageId,
versionId
);
},
getRedirectionLink: (pageId) => { getRedirectionLink: (pageId) => {
if (pageId) { if (pageId) {
return `/${workspaceSlug}/projects/${projectId}/pages/${pageId}`; return `/${workspaceSlug}/projects/${projectId}/pages/${pageId}`;
@ -119,7 +101,7 @@ const PageDetailsPage = observer(() => {
const pageRootConfig: TPageRootConfig = useMemo( const pageRootConfig: TPageRootConfig = useMemo(
() => ({ () => ({
fileHandler: getEditorFileHandlers({ fileHandler: getEditorFileHandlers({
projectId: projectId?.toString() ?? "", projectId,
uploadFile: async (blockId, file) => { uploadFile: async (blockId, file) => {
const { asset_id } = await uploadEditorAsset({ const { asset_id } = await uploadEditorAsset({
blockId, blockId,
@ -128,13 +110,13 @@ const PageDetailsPage = observer(() => {
entity_type: EFileAssetType.PAGE_DESCRIPTION, entity_type: EFileAssetType.PAGE_DESCRIPTION,
}, },
file, file,
projectId: projectId?.toString() ?? "", projectId,
workspaceSlug: workspaceSlug?.toString() ?? "", workspaceSlug,
}); });
return asset_id; return asset_id;
}, },
workspaceId, workspaceId,
workspaceSlug: workspaceSlug?.toString() ?? "", workspaceSlug,
}), }),
}), }),
[getEditorFileHandlers, id, uploadEditorAsset, projectId, workspaceId, workspaceSlug] [getEditorFileHandlers, id, uploadEditorAsset, projectId, workspaceId, workspaceSlug]
@ -143,8 +125,8 @@ const PageDetailsPage = observer(() => {
const webhookConnectionParams: TWebhookConnectionQueryParams = useMemo( const webhookConnectionParams: TWebhookConnectionQueryParams = useMemo(
() => ({ () => ({
documentType: "project_page", documentType: "project_page",
projectId: projectId?.toString() ?? "", projectId,
workspaceSlug: workspaceSlug?.toString() ?? "", workspaceSlug,
}), }),
[projectId, workspaceSlug] [projectId, workspaceSlug]
); );
@ -178,7 +160,7 @@ const PageDetailsPage = observer(() => {
</div> </div>
); );
if (!page || !workspaceSlug || !projectId) return null; if (!page) return null;
return ( return (
<> <>
@ -191,14 +173,14 @@ const PageDetailsPage = observer(() => {
storeType={storeType} storeType={storeType}
page={page} page={page}
webhookConnectionParams={webhookConnectionParams} webhookConnectionParams={webhookConnectionParams}
workspaceSlug={workspaceSlug.toString()} workspaceSlug={workspaceSlug}
projectId={projectId?.toString()} projectId={projectId}
/> />
<IssuePeekOverview /> <IssuePeekOverview />
</div> </div>
</div> </div>
</> </>
); );
}); }
export default PageDetailsPage; export default observer(PageDetailsPage);

View file

@ -1,27 +1,27 @@
"use client"; "use client";
// component // component
import { useParams } from "next/navigation"; import { Outlet } from "react-router";
import useSWR from "swr"; import useSWR from "swr";
import { AppHeader } from "@/components/core/app-header"; import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper"; import { ContentWrapper } from "@/components/core/content-wrapper";
// plane web hooks // plane web hooks
import { EPageStoreType, usePageStore } from "@/plane-web/hooks/store"; import { EPageStoreType, usePageStore } from "@/plane-web/hooks/store";
// local components // local components
import type { Route } from "./+types/layout";
import { PageDetailsHeader } from "./header"; import { PageDetailsHeader } from "./header";
export default function ProjectPageDetailsLayout({ children }: { children: React.ReactNode }) { export default function ProjectPageDetailsLayout({ params }: Route.ComponentProps) {
const { workspaceSlug, projectId } = useParams(); const { workspaceSlug, projectId } = params;
const { fetchPagesList } = usePageStore(EPageStoreType.PROJECT); const { fetchPagesList } = usePageStore(EPageStoreType.PROJECT);
// fetching pages list // fetching pages list
useSWR( useSWR(`PROJECT_PAGES_${projectId}`, () => fetchPagesList(workspaceSlug, projectId));
workspaceSlug && projectId ? `PROJECT_PAGES_${projectId}` : null,
workspaceSlug && projectId ? () => fetchPagesList(workspaceSlug.toString(), projectId.toString()) : null
);
return ( return (
<> <>
<AppHeader header={<PageDetailsHeader />} /> <AppHeader header={<PageDetailsHeader />} />
<ContentWrapper>{children}</ContentWrapper> <ContentWrapper>
<Outlet />
</ContentWrapper>
</> </>
); );
} }

View file

@ -1,17 +1,19 @@
"use client"; "use client";
import type { ReactNode } from "react";
// components // components
import { Outlet } from "react-router";
import { AppHeader } from "@/components/core/app-header"; import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper"; import { ContentWrapper } from "@/components/core/content-wrapper";
// local components // local components
import { PagesListHeader } from "./header"; import { PagesListHeader } from "./header";
export default function ProjectPagesListLayout({ children }: { children: ReactNode }) { export default function ProjectPagesListLayout() {
return ( return (
<> <>
<AppHeader header={<PagesListHeader />} /> <AppHeader header={<PagesListHeader />} />
<ContentWrapper>{children}</ContentWrapper> <ContentWrapper>
<Outlet />
</ContentWrapper>
</> </>
); );
} }

View file

@ -1,12 +1,16 @@
"use client"; "use client";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useParams, useSearchParams } from "next/navigation"; import { useSearchParams } from "next/navigation";
import { useTheme } from "next-themes";
// plane imports // plane imports
import { EUserPermissionsLevel } from "@plane/constants"; import { EUserPermissionsLevel } from "@plane/constants";
import { useTranslation } from "@plane/i18n"; import { useTranslation } from "@plane/i18n";
import type { TPageNavigationTabs } from "@plane/types"; import type { TPageNavigationTabs } from "@plane/types";
import { EUserProjectRoles } from "@plane/types"; import { EUserProjectRoles } from "@plane/types";
// assets
import darkPagesAsset from "@/app/assets/empty-state/disabled-feature/pages-dark.webp?url";
import lightPagesAsset from "@/app/assets/empty-state/disabled-feature/pages-light.webp?url";
// components // components
import { PageHead } from "@/components/core/page-title"; import { PageHead } from "@/components/core/page-title";
import { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root"; import { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root";
@ -16,35 +20,35 @@ import { PagesListView } from "@/components/pages/pages-list-view";
import { useProject } from "@/hooks/store/use-project"; import { useProject } from "@/hooks/store/use-project";
import { useUserPermissions } from "@/hooks/store/user"; import { useUserPermissions } from "@/hooks/store/user";
import { useAppRouter } from "@/hooks/use-app-router"; import { useAppRouter } from "@/hooks/use-app-router";
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
// plane web hooks // plane web hooks
import { EPageStoreType } from "@/plane-web/hooks/store"; import { EPageStoreType } from "@/plane-web/hooks/store";
import type { Route } from "./+types/page";
const ProjectPagesPage = observer(() => { const getPageType = (pageType?: string | null): TPageNavigationTabs => {
if (pageType === "private") return "private";
if (pageType === "archived") return "archived";
return "public";
};
function ProjectPagesPage({ params }: Route.ComponentProps) {
// router // router
const router = useAppRouter(); const router = useAppRouter();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const type = searchParams.get("type"); const type = searchParams.get("type");
const { workspaceSlug, projectId } = useParams(); const { workspaceSlug, projectId } = params;
// theme hook
const { resolvedTheme } = useTheme();
// plane hooks // plane hooks
const { t } = useTranslation(); const { t } = useTranslation();
// store hooks // store hooks
const { getProjectById, currentProjectDetails } = useProject(); const { getProjectById, currentProjectDetails } = useProject();
const { allowPermissions } = useUserPermissions(); const { allowPermissions } = useUserPermissions();
// derived values // derived values
const project = projectId ? getProjectById(projectId.toString()) : undefined; const project = getProjectById(projectId);
const pageTitle = project?.name ? `${project?.name} - Pages` : undefined; const pageTitle = project?.name ? `${project?.name} - Pages` : undefined;
const canPerformEmptyStateActions = allowPermissions([EUserProjectRoles.ADMIN], EUserPermissionsLevel.PROJECT); const canPerformEmptyStateActions = allowPermissions([EUserProjectRoles.ADMIN], EUserPermissionsLevel.PROJECT);
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/disabled-feature/pages" }); const resolvedPath = resolvedTheme === "light" ? lightPagesAsset : darkPagesAsset;
const pageType = getPageType(type);
const currentPageType = (): TPageNavigationTabs => {
const pageType = type?.toString();
if (pageType === "private") return "private";
if (pageType === "archived") return "archived";
return "public";
};
if (!workspaceSlug || !projectId) return <></>;
// No access to cycle // No access to cycle
if (currentProjectDetails?.page_view === false) if (currentProjectDetails?.page_view === false)
@ -68,15 +72,15 @@ const ProjectPagesPage = observer(() => {
<> <>
<PageHead title={pageTitle} /> <PageHead title={pageTitle} />
<PagesListView <PagesListView
pageType={currentPageType()} pageType={pageType}
projectId={projectId.toString()} projectId={projectId}
storeType={EPageStoreType.PROJECT} storeType={EPageStoreType.PROJECT}
workspaceSlug={workspaceSlug.toString()} workspaceSlug={workspaceSlug}
> >
<PagesListRoot pageType={currentPageType()} storeType={EPageStoreType.PROJECT} /> <PagesListRoot pageType={pageType} storeType={EPageStoreType.PROJECT} />
</PagesListView> </PagesListView>
</> </>
); );
}); }
export default ProjectPagesPage; export default observer(ProjectPagesPage);

View file

@ -1,8 +1,9 @@
"use client"; "use client";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import useSWR from "swr"; import useSWR from "swr";
// assets
import emptyView from "@/app/assets/empty-state/view.svg?url";
// components // components
import { EmptyState } from "@/components/common/empty-state"; import { EmptyState } from "@/components/common/empty-state";
import { PageHead } from "@/components/core/page-title"; import { PageHead } from "@/components/core/page-title";
@ -10,28 +11,22 @@ import { ProjectViewLayoutRoot } from "@/components/issues/issue-layouts/roots/p
// hooks // hooks
import { useProject } from "@/hooks/store/use-project"; import { useProject } from "@/hooks/store/use-project";
import { useProjectView } from "@/hooks/store/use-project-view"; import { useProjectView } from "@/hooks/store/use-project-view";
// assets
import { useAppRouter } from "@/hooks/use-app-router"; import { useAppRouter } from "@/hooks/use-app-router";
import emptyView from "@/public/empty-state/view.svg"; import type { Route } from "./+types/page";
const ProjectViewIssuesPage = observer(() => { function ProjectViewIssuesPage({ params }: Route.ComponentProps) {
// router // router
const router = useAppRouter(); const router = useAppRouter();
const { workspaceSlug, projectId, viewId } = useParams(); const { workspaceSlug, projectId, viewId } = params;
// store hooks // store hooks
const { fetchViewDetails, getViewById } = useProjectView(); const { fetchViewDetails, getViewById } = useProjectView();
const { getProjectById } = useProject(); const { getProjectById } = useProject();
// derived values // derived values
const projectView = viewId ? getViewById(viewId.toString()) : undefined; const projectView = getViewById(viewId);
const project = projectId ? getProjectById(projectId.toString()) : undefined; const project = getProjectById(projectId);
const pageTitle = project?.name && projectView?.name ? `${project?.name} - ${projectView?.name}` : undefined; const pageTitle = project?.name && projectView?.name ? `${project?.name} - ${projectView?.name}` : undefined;
const { error } = useSWR( const { error } = useSWR(`VIEW_DETAILS_${viewId}`, () => fetchViewDetails(workspaceSlug, projectId, viewId));
workspaceSlug && projectId && viewId ? `VIEW_DETAILS_${viewId.toString()}` : null,
workspaceSlug && projectId && viewId
? () => fetchViewDetails(workspaceSlug.toString(), projectId.toString(), viewId.toString())
: null
);
if (error) { if (error) {
return ( return (
@ -53,6 +48,6 @@ const ProjectViewIssuesPage = observer(() => {
<ProjectViewLayoutRoot /> <ProjectViewLayoutRoot />
</> </>
); );
}); }
export default ProjectViewIssuesPage; export default observer(ProjectViewIssuesPage);

View file

@ -1,15 +1,18 @@
"use client"; "use client";
import { Outlet } from "react-router";
import { AppHeader } from "@/components/core/app-header"; import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper"; import { ContentWrapper } from "@/components/core/content-wrapper";
// local components // local components
import { ProjectViewIssuesHeader } from "./[viewId]/header"; import { ProjectViewIssuesHeader } from "./[viewId]/header";
export default function ProjectViewIssuesLayout({ children }: { children: React.ReactNode }) { export default function ProjectViewIssuesLayout() {
return ( return (
<> <>
<AppHeader header={<ProjectViewIssuesHeader />} /> <AppHeader header={<ProjectViewIssuesHeader />} />
<ContentWrapper>{children}</ContentWrapper> <ContentWrapper>
<Outlet />
</ContentWrapper>
</> </>
); );
} }

View file

@ -1,16 +1,19 @@
"use client"; "use client";
import { Outlet } from "react-router";
import { AppHeader } from "@/components/core/app-header"; import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper"; import { ContentWrapper } from "@/components/core/content-wrapper";
// local components // local components
import { ProjectViewsHeader } from "./header"; import { ProjectViewsHeader } from "./header";
import { ViewMobileHeader } from "./mobile-header"; import { ViewMobileHeader } from "./mobile-header";
export default function ProjectViewsListLayout({ children }: { children: React.ReactNode }) { export default function ProjectViewsListLayout() {
return ( return (
<> <>
<AppHeader header={<ProjectViewsHeader />} mobileHeader={<ViewMobileHeader />} /> <AppHeader header={<ProjectViewsHeader />} mobileHeader={<ViewMobileHeader />} />
<ContentWrapper>{children}</ContentWrapper> <ContentWrapper>
<Outlet />
</ContentWrapper>
</> </>
); );
} }

View file

@ -2,31 +2,35 @@
import { useCallback } from "react"; import { useCallback } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useParams } from "next/navigation"; import { useTheme } from "next-themes";
// components // plane imports
import { EUserPermissionsLevel } from "@plane/constants"; import { EUserPermissionsLevel } from "@plane/constants";
import { useTranslation } from "@plane/i18n"; import { useTranslation } from "@plane/i18n";
import type { EViewAccess, TViewFilterProps } from "@plane/types"; import type { EViewAccess, TViewFilterProps } from "@plane/types";
import { EUserProjectRoles } from "@plane/types"; import { EUserProjectRoles } from "@plane/types";
import { Header, EHeaderVariant } from "@plane/ui"; import { Header, EHeaderVariant } from "@plane/ui";
import { calculateTotalFilters } from "@plane/utils"; import { calculateTotalFilters } from "@plane/utils";
// assets
import darkViewsAsset from "@/app/assets/empty-state/disabled-feature/views-dark.webp?url";
import lightViewsAsset from "@/app/assets/empty-state/disabled-feature/views-light.webp?url";
// components
import { PageHead } from "@/components/core/page-title"; import { PageHead } from "@/components/core/page-title";
import { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root"; import { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root";
import { ViewAppliedFiltersList } from "@/components/views/applied-filters"; import { ViewAppliedFiltersList } from "@/components/views/applied-filters";
import { ProjectViewsList } from "@/components/views/views-list"; import { ProjectViewsList } from "@/components/views/views-list";
// constants
// helpers
// hooks // hooks
import { useProject } from "@/hooks/store/use-project"; import { useProject } from "@/hooks/store/use-project";
import { useProjectView } from "@/hooks/store/use-project-view"; import { useProjectView } from "@/hooks/store/use-project-view";
import { useUserPermissions } from "@/hooks/store/user"; import { useUserPermissions } from "@/hooks/store/user";
import { useAppRouter } from "@/hooks/use-app-router"; import { useAppRouter } from "@/hooks/use-app-router";
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; import type { Route } from "./+types/page";
const ProjectViewsPage = observer(() => { function ProjectViewsPage({ params }: Route.ComponentProps) {
// router // router
const router = useAppRouter(); const router = useAppRouter();
const { workspaceSlug, projectId } = useParams(); const { workspaceSlug, projectId } = params;
// theme hook
const { resolvedTheme } = useTheme();
// plane hooks // plane hooks
const { t } = useTranslation(); const { t } = useTranslation();
// store // store
@ -34,10 +38,10 @@ const ProjectViewsPage = observer(() => {
const { filters, updateFilters, clearAllFilters } = useProjectView(); const { filters, updateFilters, clearAllFilters } = useProjectView();
const { allowPermissions } = useUserPermissions(); const { allowPermissions } = useUserPermissions();
// derived values // derived values
const project = projectId ? getProjectById(projectId.toString()) : undefined; const project = getProjectById(projectId);
const pageTitle = project?.name ? `${project?.name} - Views` : undefined; const pageTitle = project?.name ? `${project?.name} - Views` : undefined;
const canPerformEmptyStateActions = allowPermissions([EUserProjectRoles.ADMIN], EUserPermissionsLevel.PROJECT); const canPerformEmptyStateActions = allowPermissions([EUserProjectRoles.ADMIN], EUserPermissionsLevel.PROJECT);
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/disabled-feature/views" }); const resolvedPath = resolvedTheme === "light" ? lightViewsAsset : darkViewsAsset;
const handleRemoveFilter = useCallback( const handleRemoveFilter = useCallback(
(key: keyof TViewFilterProps, value: string | EViewAccess | null) => { (key: keyof TViewFilterProps, value: string | EViewAccess | null) => {
@ -58,8 +62,6 @@ const ProjectViewsPage = observer(() => {
const isFiltersApplied = calculateTotalFilters(filters?.filters ?? {}) !== 0; const isFiltersApplied = calculateTotalFilters(filters?.filters ?? {}) !== 0;
if (!workspaceSlug || !projectId) return <></>;
// No access to // No access to
if (currentProjectDetails?.issue_views_view === false) if (currentProjectDetails?.issue_views_view === false)
return ( return (
@ -95,6 +97,6 @@ const ProjectViewsPage = observer(() => {
<ProjectViewsList /> <ProjectViewsList />
</> </>
); );
}); }
export default ProjectViewsPage; export default observer(ProjectViewsPage);

View file

@ -1,17 +1,20 @@
"use client"; "use client";
import type { ReactNode } from "react";
// components // components
import { Outlet } from "react-router";
import { AppHeader } from "@/components/core/app-header"; import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper"; import { ContentWrapper } from "@/components/core/content-wrapper";
// local components // local components
import { ProjectsListHeader } from "@/plane-web/components/projects/header"; import { ProjectsListHeader } from "@/plane-web/components/projects/header";
import { ProjectsListMobileHeader } from "@/plane-web/components/projects/mobile-header"; import { ProjectsListMobileHeader } from "@/plane-web/components/projects/mobile-header";
export default function ProjectListLayout({ children }: { children: ReactNode }) {
export default function ProjectListLayout() {
return ( return (
<> <>
<AppHeader header={<ProjectsListHeader />} mobileHeader={<ProjectsListMobileHeader />} /> <AppHeader header={<ProjectsListHeader />} mobileHeader={<ProjectsListMobileHeader />} />
<ContentWrapper>{children}</ContentWrapper> <ContentWrapper>
<Outlet />
</ContentWrapper>
</> </>
); );
} }

View file

@ -1,18 +1,16 @@
"use client"; "use client";
import type { ReactNode } from "react"; import { Outlet } from "react-router";
import { useParams } from "next/navigation";
// plane web layouts // plane web layouts
import { ProjectAuthWrapper } from "@/plane-web/layouts/project-wrapper"; import { ProjectAuthWrapper } from "@/plane-web/layouts/project-wrapper";
import type { Route } from "./+types/layout";
const ProjectDetailLayout = ({ children }: { children: ReactNode }) => { export default function ProjectDetailLayout({ params }: Route.ComponentProps) {
// router // router
const { workspaceSlug, projectId } = useParams(); const { workspaceSlug, projectId } = params;
return ( return (
<ProjectAuthWrapper workspaceSlug={workspaceSlug?.toString()} projectId={projectId?.toString()}> <ProjectAuthWrapper workspaceSlug={workspaceSlug} projectId={projectId}>
{children} <Outlet />
</ProjectAuthWrapper> </ProjectAuthWrapper>
); );
}; }
export default ProjectDetailLayout;

View file

@ -1,17 +1,20 @@
"use client"; "use client";
import type { ReactNode } from "react"; import { Outlet } from "react-router";
// components // components
import { AppHeader } from "@/components/core/app-header"; import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper"; import { ContentWrapper } from "@/components/core/content-wrapper";
// local components // local components
import { ProjectsListHeader } from "@/plane-web/components/projects/header"; import { ProjectsListHeader } from "@/plane-web/components/projects/header";
import { ProjectsListMobileHeader } from "@/plane-web/components/projects/mobile-header"; import { ProjectsListMobileHeader } from "@/plane-web/components/projects/mobile-header";
export default function ProjectListLayout({ children }: { children: ReactNode }) {
export default function ProjectListLayout() {
return ( return (
<> <>
<AppHeader header={<ProjectsListHeader />} mobileHeader={<ProjectsListMobileHeader />} /> <AppHeader header={<ProjectsListHeader />} mobileHeader={<ProjectsListMobileHeader />} />
<ContentWrapper>{children}</ContentWrapper> <ContentWrapper>
<Outlet />
</ContentWrapper>
</> </>
); );
} }

View file

@ -5,11 +5,11 @@ import { useTheme } from "next-themes";
// plane imports // plane imports
import { HEADER_GITHUB_ICON, GITHUB_REDIRECTED_TRACKER_EVENT } from "@plane/constants"; import { HEADER_GITHUB_ICON, GITHUB_REDIRECTED_TRACKER_EVENT } from "@plane/constants";
import { useTranslation } from "@plane/i18n"; import { useTranslation } from "@plane/i18n";
// assets
import githubBlackImage from "@/app/assets/logos/github-black.png?url";
import githubWhiteImage from "@/app/assets/logos/github-white.png?url";
// helpers // helpers
import { captureElementAndEvent } from "@/helpers/event-tracker.helper"; 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";
export const StarUsOnGitHubLink = () => { export const StarUsOnGitHubLink = () => {
// plane hooks // plane hooks

View file

@ -1,14 +1,17 @@
"use client"; "use client";
import { Outlet } from "react-router";
import { AppHeader } from "@/components/core/app-header"; import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper"; import { ContentWrapper } from "@/components/core/content-wrapper";
import { WorkspaceStickyHeader } from "./header"; import { WorkspaceStickyHeader } from "./header";
export default function WorkspaceStickiesLayout({ children }: { children: React.ReactNode }) { export default function WorkspaceStickiesLayout() {
return ( return (
<> <>
<AppHeader header={<WorkspaceStickyHeader />} /> <AppHeader header={<WorkspaceStickyHeader />} />
<ContentWrapper>{children}</ContentWrapper> <ContentWrapper>
<Outlet />
</ContentWrapper>
</> </>
); );
} }

View file

@ -2,7 +2,6 @@
import { useState } from "react"; import { useState } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// plane imports // plane imports
import { DEFAULT_GLOBAL_VIEWS_LIST } from "@plane/constants"; import { DEFAULT_GLOBAL_VIEWS_LIST } from "@plane/constants";
// components // components
@ -10,10 +9,11 @@ import { PageHead } from "@/components/core/page-title";
import { AllIssueLayoutRoot } from "@/components/issues/issue-layouts/roots/all-issue-layout-root"; import { AllIssueLayoutRoot } from "@/components/issues/issue-layouts/roots/all-issue-layout-root";
// hooks // hooks
import { useWorkspace } from "@/hooks/store/use-workspace"; import { useWorkspace } from "@/hooks/store/use-workspace";
import type { Route } from "./+types/page";
const GlobalViewIssuesPage = observer(() => { function GlobalViewIssuesPage({ params }: Route.ComponentProps) {
// router // router
const { globalViewId } = useParams(); const { globalViewId } = params;
// store hooks // store hooks
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
// states // states
@ -31,6 +31,6 @@ const GlobalViewIssuesPage = observer(() => {
<AllIssueLayoutRoot isDefaultView={!!defaultView} isLoading={isLoading} toggleLoading={toggleLoading} /> <AllIssueLayoutRoot isDefaultView={!!defaultView} isLoading={isLoading} toggleLoading={toggleLoading} />
</> </>
); );
}); }
export default GlobalViewIssuesPage; export default observer(GlobalViewIssuesPage);

View file

@ -1,14 +1,17 @@
"use client"; "use client";
import { Outlet } from "react-router";
import { AppHeader } from "@/components/core/app-header"; import { AppHeader } from "@/components/core/app-header";
import { ContentWrapper } from "@/components/core/content-wrapper"; import { ContentWrapper } from "@/components/core/content-wrapper";
import { GlobalIssuesHeader } from "./header"; import { GlobalIssuesHeader } from "./header";
export default function GlobalIssuesLayout({ children }: { children: React.ReactNode }) { export default function GlobalIssuesLayout() {
return ( return (
<> <>
<AppHeader header={<GlobalIssuesHeader />} /> <AppHeader header={<GlobalIssuesHeader />} />
<ContentWrapper>{children}</ContentWrapper> <ContentWrapper>
<Outlet />
</ContentWrapper>
</> </>
); );
} }

View file

@ -14,7 +14,7 @@ import { GlobalViewsList } from "@/components/workspace/views/views-list";
// hooks // hooks
import { useWorkspace } from "@/hooks/store/use-workspace"; import { useWorkspace } from "@/hooks/store/use-workspace";
const WorkspaceViewsPage = observer(() => { function WorkspaceViewsPage() {
const [query, setQuery] = useState(""); const [query, setQuery] = useState("");
// store // store
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
@ -47,6 +47,6 @@ const WorkspaceViewsPage = observer(() => {
</div> </div>
</> </>
); );
}); }
export default WorkspaceViewsPage; export default observer(WorkspaceViewsPage);

View file

@ -1,12 +1,14 @@
"use client"; "use client";
import { Outlet } from "react-router";
// components
import { ContentWrapper } from "@/components/core/content-wrapper"; import { ContentWrapper } from "@/components/core/content-wrapper";
import { ProjectsAppPowerKProvider } from "@/components/power-k/projects-app-provider"; import { ProjectsAppPowerKProvider } from "@/components/power-k/projects-app-provider";
import { SettingsHeader } from "@/components/settings/header"; import { SettingsHeader } from "@/components/settings/header";
import { AuthenticationWrapper } from "@/lib/wrappers/authentication-wrapper"; import { AuthenticationWrapper } from "@/lib/wrappers/authentication-wrapper";
import { WorkspaceAuthWrapper } from "@/plane-web/layouts/workspace-wrapper"; import { WorkspaceAuthWrapper } from "@/plane-web/layouts/workspace-wrapper";
export default function SettingsLayout({ children }: { children: React.ReactNode }) { export default function SettingsLayout() {
return ( return (
<AuthenticationWrapper> <AuthenticationWrapper>
<WorkspaceAuthWrapper> <WorkspaceAuthWrapper>
@ -17,7 +19,9 @@ export default function SettingsLayout({ children }: { children: React.ReactNode
<SettingsHeader /> <SettingsHeader />
{/* Content */} {/* Content */}
<ContentWrapper className="p-page-x md:flex w-full"> <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> </ContentWrapper>
</main> </main>
</div> </div>

View file

@ -12,7 +12,7 @@ import { useUserPermissions } from "@/hooks/store/user";
// plane web components // plane web components
import { BillingRoot } from "@/plane-web/components/workspace/billing"; import { BillingRoot } from "@/plane-web/components/workspace/billing";
const BillingSettingsPage = observer(() => { function BillingSettingsPage() {
// store hooks // store hooks
const { workspaceUserInfo, allowPermissions } = useUserPermissions(); const { workspaceUserInfo, allowPermissions } = useUserPermissions();
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
@ -30,6 +30,6 @@ const BillingSettingsPage = observer(() => {
<BillingRoot /> <BillingRoot />
</SettingsContentWrapper> </SettingsContentWrapper>
); );
}); }
export default BillingSettingsPage; export default observer(BillingSettingsPage);

View file

@ -15,7 +15,7 @@ import SettingsHeading from "@/components/settings/heading";
import { useWorkspace } from "@/hooks/store/use-workspace"; import { useWorkspace } from "@/hooks/store/use-workspace";
import { useUserPermissions } from "@/hooks/store/user"; import { useUserPermissions } from "@/hooks/store/user";
const ExportsPage = observer(() => { function ExportsPage() {
// store hooks // store hooks
const { workspaceUserInfo, allowPermissions } = useUserPermissions(); const { workspaceUserInfo, allowPermissions } = useUserPermissions();
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
@ -51,6 +51,6 @@ const ExportsPage = observer(() => {
</div> </div>
</SettingsContentWrapper> </SettingsContentWrapper>
); );
}); }
export default ExportsPage; export default observer(ExportsPage);

View file

@ -12,7 +12,7 @@ import { SettingsHeading } from "@/components/settings/heading";
import { useWorkspace } from "@/hooks/store/use-workspace"; import { useWorkspace } from "@/hooks/store/use-workspace";
import { useUserPermissions } from "@/hooks/store/user"; import { useUserPermissions } from "@/hooks/store/user";
const ImportsPage = observer(() => { function ImportsPage() {
// router // router
// store hooks // store hooks
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
@ -32,6 +32,6 @@ const ImportsPage = observer(() => {
</section> </section>
</SettingsContentWrapper> </SettingsContentWrapper>
); );
}); }
export default ImportsPage; export default observer(ImportsPage);

View file

@ -1,6 +1,5 @@
"use client"; "use client";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import useSWR from "swr"; import useSWR from "swr";
// components // components
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
@ -20,9 +19,7 @@ import { IntegrationService } from "@/services/integrations";
const integrationService = new IntegrationService(); const integrationService = new IntegrationService();
const WorkspaceIntegrationsPage = observer(() => { function WorkspaceIntegrationsPage() {
// router
const { workspaceSlug } = useParams();
// store hooks // store hooks
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
const { allowPermissions } = useUserPermissions(); const { allowPermissions } = useUserPermissions();
@ -30,8 +27,8 @@ const WorkspaceIntegrationsPage = observer(() => {
// derived values // derived values
const isAdmin = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.WORKSPACE); const isAdmin = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.WORKSPACE);
const pageTitle = currentWorkspace?.name ? `${currentWorkspace.name} - Integrations` : undefined; const pageTitle = currentWorkspace?.name ? `${currentWorkspace.name} - Integrations` : undefined;
const { data: appIntegrations } = useSWR(workspaceSlug && isAdmin ? APP_INTEGRATIONS : null, () => const { data: appIntegrations } = useSWR(isAdmin ? APP_INTEGRATIONS : null, () =>
workspaceSlug && isAdmin ? integrationService.getAppIntegrationsList() : null isAdmin ? integrationService.getAppIntegrationsList() : null
); );
if (!isAdmin) return <NotAuthorizedView section="settings" className="h-auto" />; if (!isAdmin) return <NotAuthorizedView section="settings" className="h-auto" />;
@ -53,6 +50,6 @@ const WorkspaceIntegrationsPage = observer(() => {
</section> </section>
</SettingsContentWrapper> </SettingsContentWrapper>
); );
}); }
export default WorkspaceIntegrationsPage; export default observer(WorkspaceIntegrationsPage);

View file

@ -1,8 +1,8 @@
"use client"; "use client";
import type { FC, ReactNode } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
import { Outlet } from "react-router";
// constants // constants
import { WORKSPACE_SETTINGS_ACCESS } from "@plane/constants"; import { WORKSPACE_SETTINGS_ACCESS } from "@plane/constants";
import type { EUserWorkspaceRoles } from "@plane/types"; import type { EUserWorkspaceRoles } from "@plane/types";
@ -15,12 +15,7 @@ import { useUserPermissions } from "@/hooks/store/user";
// local components // local components
import { WorkspaceSettingsSidebar } from "./sidebar"; import { WorkspaceSettingsSidebar } from "./sidebar";
export interface IWorkspaceSettingLayout { function WorkspaceSettingLayout() {
children: ReactNode;
}
const WorkspaceSettingLayout: FC<IWorkspaceSettingLayout> = observer((props) => {
const { children } = props;
// store hooks // store hooks
const { workspaceUserInfo, getWorkspaceRoleByWorkspaceSlug } = useUserPermissions(); const { workspaceUserInfo, getWorkspaceRoleByWorkspaceSlug } = useUserPermissions();
// next hooks // next hooks
@ -46,12 +41,14 @@ const WorkspaceSettingLayout: FC<IWorkspaceSettingLayout> = observer((props) =>
) : ( ) : (
<div className="relative flex h-full w-full"> <div className="relative flex h-full w-full">
<div className="hidden md:block">{<WorkspaceSettingsSidebar />}</div> <div className="hidden md:block">{<WorkspaceSettingsSidebar />}</div>
<div className="w-full h-full overflow-y-scroll md:pt-page-y">{children}</div> <div className="w-full h-full overflow-y-scroll md:pt-page-y">
<Outlet />
</div>
</div> </div>
)} )}
</div> </div>
</> </>
); );
}); }
export default WorkspaceSettingLayout; export default observer(WorkspaceSettingLayout);

View file

@ -2,7 +2,6 @@
import { useState } from "react"; import { useState } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import { Search } from "lucide-react"; import { Search } from "lucide-react";
// types // types
import { import {
@ -32,13 +31,14 @@ import { useUserPermissions } from "@/hooks/store/user";
// plane web components // plane web components
import { BillingActionsButton } from "@/plane-web/components/workspace/billing/billing-actions-button"; import { BillingActionsButton } from "@/plane-web/components/workspace/billing/billing-actions-button";
import { SendWorkspaceInvitationModal } from "@/plane-web/components/workspace/members/invite-modal"; import { SendWorkspaceInvitationModal } from "@/plane-web/components/workspace/members/invite-modal";
import type { Route } from "./+types/page";
const WorkspaceMembersSettingsPage = observer(() => { function WorkspaceMembersSettingsPage({ params }: Route.ComponentProps) {
// states // states
const [inviteModal, setInviteModal] = useState(false); const [inviteModal, setInviteModal] = useState(false);
const [searchQuery, setSearchQuery] = useState<string>(""); const [searchQuery, setSearchQuery] = useState<string>("");
// router // router
const { workspaceSlug } = useParams(); const { workspaceSlug } = params;
// store hooks // store hooks
const { workspaceUserInfo, allowPermissions } = useUserPermissions(); const { workspaceUserInfo, allowPermissions } = useUserPermissions();
const { const {
@ -54,39 +54,42 @@ const WorkspaceMembersSettingsPage = observer(() => {
EUserPermissionsLevel.WORKSPACE EUserPermissionsLevel.WORKSPACE
); );
const handleWorkspaceInvite = (data: IWorkspaceBulkInviteFormData) => { const handleWorkspaceInvite = async (data: IWorkspaceBulkInviteFormData) => {
if (!workspaceSlug) return; try {
await inviteMembersToWorkspace(workspaceSlug, data);
return inviteMembersToWorkspace(workspaceSlug.toString(), data)
.then(() => {
setInviteModal(false); setInviteModal(false);
captureSuccess({ captureSuccess({
eventName: MEMBER_TRACKER_EVENTS.invite, eventName: MEMBER_TRACKER_EVENTS.invite,
payload: { payload: {
emails: [...data.emails.map((email) => email.email)], emails: data.emails.map((email) => email.email),
}, },
}); });
setToast({ setToast({
type: TOAST_TYPE.SUCCESS, type: TOAST_TYPE.SUCCESS,
title: "Success!", title: "Success!",
message: t("workspace_settings.settings.members.invitations_sent_successfully"), message: t("workspace_settings.settings.members.invitations_sent_successfully"),
}); });
}) // eslint-disable-next-line @typescript-eslint/no-explicit-any
.catch((err) => { } catch (err: any) {
captureError({ captureError({
eventName: MEMBER_TRACKER_EVENTS.invite, eventName: MEMBER_TRACKER_EVENTS.invite,
payload: { payload: {
emails: [...data.emails.map((email) => email.email)], emails: data.emails.map((email) => email.email),
}, },
error: err, error: err,
}); });
setToast({ setToast({
type: TOAST_TYPE.ERROR, type: TOAST_TYPE.ERROR,
title: "Error!", title: "Error!",
message: `${err.error ?? t("something_went_wrong_please_try_again")}`, message: `${err.error ?? t("something_went_wrong_please_try_again")}`,
}); });
throw err; throw err;
}); }
}; };
// Handler for role filter updates // Handler for role filter updates
@ -162,6 +165,6 @@ const WorkspaceMembersSettingsPage = observer(() => {
</section> </section>
</SettingsContentWrapper> </SettingsContentWrapper>
); );
}); }
export default WorkspaceMembersSettingsPage; export default observer(WorkspaceMembersSettingsPage);

View file

@ -10,7 +10,7 @@ import { WorkspaceDetails } from "@/components/workspace/settings/workspace-deta
// hooks // hooks
import { useWorkspace } from "@/hooks/store/use-workspace"; import { useWorkspace } from "@/hooks/store/use-workspace";
const WorkspaceSettingsPage = observer(() => { function WorkspaceSettingsPage() {
// store hooks // store hooks
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
const { t } = useTranslation(); const { t } = useTranslation();
@ -25,6 +25,6 @@ const WorkspaceSettingsPage = observer(() => {
<WorkspaceDetails /> <WorkspaceDetails />
</SettingsContentWrapper> </SettingsContentWrapper>
); );
}); }
export default WorkspaceSettingsPage; export default observer(WorkspaceSettingsPage);

View file

@ -2,7 +2,6 @@
import { useState } from "react"; import { useState } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import useSWR from "swr"; import useSWR from "swr";
import { EUserPermissions, EUserPermissionsLevel, WORKSPACE_SETTINGS_TRACKER_EVENTS } from "@plane/constants"; import { EUserPermissions, EUserPermissionsLevel, WORKSPACE_SETTINGS_TRACKER_EVENTS } from "@plane/constants";
import { TOAST_TYPE, setToast } from "@plane/propel/toast"; import { TOAST_TYPE, setToast } from "@plane/propel/toast";
@ -18,12 +17,13 @@ import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
import { useWebhook } from "@/hooks/store/use-webhook"; import { useWebhook } from "@/hooks/store/use-webhook";
import { useWorkspace } from "@/hooks/store/use-workspace"; import { useWorkspace } from "@/hooks/store/use-workspace";
import { useUserPermissions } from "@/hooks/store/user"; import { useUserPermissions } from "@/hooks/store/user";
import type { Route } from "./+types/page";
const WebhookDetailsPage = observer(() => { function WebhookDetailsPage({ params }: Route.ComponentProps) {
// states // states
const [deleteWebhookModal, setDeleteWebhookModal] = useState(false); const [deleteWebhookModal, setDeleteWebhookModal] = useState(false);
// router // router
const { workspaceSlug, webhookId } = useParams(); const { workspaceSlug, webhookId } = params;
// mobx store // mobx store
const { currentWebhook, fetchWebhookById, updateWebhook } = useWebhook(); const { currentWebhook, fetchWebhookById, updateWebhook } = useWebhook();
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
@ -33,57 +33,55 @@ const WebhookDetailsPage = observer(() => {
// useEffect(() => { // useEffect(() => {
// if (isCreated !== "true") clearSecretKey(); // if (isCreated !== "true") clearSecretKey();
// }, [clearSecretKey, isCreated]); // }, [clearSecretKey, isCreated]);
// derived values // derived values
const isAdmin = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.WORKSPACE); const isAdmin = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.WORKSPACE);
const pageTitle = currentWorkspace?.name ? `${currentWorkspace.name} - Webhook` : undefined; const pageTitle = currentWorkspace?.name ? `${currentWorkspace.name} - Webhook` : undefined;
useSWR( useSWR(
workspaceSlug && webhookId && isAdmin ? `WEBHOOK_DETAILS_${workspaceSlug}_${webhookId}` : null, isAdmin ? `WEBHOOK_DETAILS_${workspaceSlug}_${webhookId}` : null,
workspaceSlug && webhookId && isAdmin isAdmin ? () => fetchWebhookById(workspaceSlug, webhookId) : null
? () => fetchWebhookById(workspaceSlug.toString(), webhookId.toString())
: null
); );
const handleUpdateWebhook = async (formData: IWebhook) => { const handleUpdateWebhook = async (formData: IWebhook) => {
if (!workspaceSlug || !formData || !formData.id) return; if (!formData || !formData.id) return;
const payload = { const payload = {
url: formData?.url, url: formData.url,
is_active: formData?.is_active, is_active: formData.is_active,
project: formData?.project, project: formData.project,
cycle: formData?.cycle, cycle: formData.cycle,
module: formData?.module, module: formData.module,
issue: formData?.issue, issue: formData.issue,
issue_comment: formData?.issue_comment, issue_comment: formData.issue_comment,
}; };
await updateWebhook(workspaceSlug.toString(), formData.id, payload)
.then(() => { try {
await updateWebhook(workspaceSlug, formData.id, payload);
captureSuccess({ captureSuccess({
eventName: WORKSPACE_SETTINGS_TRACKER_EVENTS.webhook_updated, eventName: WORKSPACE_SETTINGS_TRACKER_EVENTS.webhook_updated,
payload: { payload: { webhook: formData.id },
webhook: formData.id,
},
}); });
setToast({ setToast({
type: TOAST_TYPE.SUCCESS, type: TOAST_TYPE.SUCCESS,
title: "Success!", title: "Success!",
message: "Webhook updated successfully.", message: "Webhook updated successfully.",
}); });
}) // eslint-disable-next-line @typescript-eslint/no-explicit-any
.catch((error) => { } catch (error: any) {
captureError({ captureError({
eventName: WORKSPACE_SETTINGS_TRACKER_EVENTS.webhook_updated, eventName: WORKSPACE_SETTINGS_TRACKER_EVENTS.webhook_updated,
payload: { payload: { webhook: formData.id },
webhook: formData.id,
},
error: error as Error, error: error as Error,
}); });
setToast({ setToast({
type: TOAST_TYPE.ERROR, type: TOAST_TYPE.ERROR,
title: "Error!", title: "Error!",
message: error?.error ?? "Something went wrong. Please try again.", message: error?.error ?? "Something went wrong. Please try again.",
}); });
}); }
}; };
if (!isAdmin) if (!isAdmin)
@ -108,13 +106,13 @@ const WebhookDetailsPage = observer(() => {
<PageHead title={pageTitle} /> <PageHead title={pageTitle} />
<DeleteWebhookModal isOpen={deleteWebhookModal} onClose={() => setDeleteWebhookModal(false)} /> <DeleteWebhookModal isOpen={deleteWebhookModal} onClose={() => setDeleteWebhookModal(false)} />
<div className="w-full space-y-8 overflow-y-auto"> <div className="w-full space-y-8 overflow-y-auto">
<div className=""> <div>
<WebhookForm onSubmit={async (data) => await handleUpdateWebhook(data)} data={currentWebhook} /> <WebhookForm onSubmit={handleUpdateWebhook} data={currentWebhook} />
</div> </div>
{currentWebhook && <WebhookDeleteSection openDeleteModal={() => setDeleteWebhookModal(true)} />} {currentWebhook && <WebhookDeleteSection openDeleteModal={() => setDeleteWebhookModal(true)} />}
</div> </div>
</SettingsContentWrapper> </SettingsContentWrapper>
); );
}); }
export default WebhookDetailsPage; export default observer(WebhookDetailsPage);

View file

@ -1,8 +1,7 @@
"use client"; "use client";
import React, { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import useSWR from "swr"; import useSWR from "swr";
// plane imports // plane imports
import { EUserPermissions, EUserPermissionsLevel, WORKSPACE_SETTINGS_TRACKER_ELEMENTS } from "@plane/constants"; import { EUserPermissions, EUserPermissionsLevel, WORKSPACE_SETTINGS_TRACKER_ELEMENTS } from "@plane/constants";
@ -20,12 +19,13 @@ import { captureClick } from "@/helpers/event-tracker.helper";
import { useWebhook } from "@/hooks/store/use-webhook"; import { useWebhook } from "@/hooks/store/use-webhook";
import { useWorkspace } from "@/hooks/store/use-workspace"; import { useWorkspace } from "@/hooks/store/use-workspace";
import { useUserPermissions } from "@/hooks/store/user"; import { useUserPermissions } from "@/hooks/store/user";
import type { Route } from "./+types/page";
const WebhooksListPage = observer(() => { function WebhooksListPage({ params }: Route.ComponentProps) {
// states // states
const [showCreateWebhookModal, setShowCreateWebhookModal] = useState(false); const [showCreateWebhookModal, setShowCreateWebhookModal] = useState(false);
// router // router
const { workspaceSlug } = useParams(); const { workspaceSlug } = params;
// plane hooks // plane hooks
const { t } = useTranslation(); const { t } = useTranslation();
// mobx store // mobx store
@ -36,8 +36,8 @@ const WebhooksListPage = observer(() => {
const canPerformWorkspaceAdminActions = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.WORKSPACE); const canPerformWorkspaceAdminActions = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.WORKSPACE);
useSWR( useSWR(
workspaceSlug && canPerformWorkspaceAdminActions ? `WEBHOOKS_LIST_${workspaceSlug}` : null, canPerformWorkspaceAdminActions ? `WEBHOOKS_LIST_${workspaceSlug}` : null,
workspaceSlug && canPerformWorkspaceAdminActions ? () => fetchWebhooks(workspaceSlug.toString()) : null canPerformWorkspaceAdminActions ? () => fetchWebhooks(workspaceSlug) : null
); );
const pageTitle = currentWorkspace?.name const pageTitle = currentWorkspace?.name
@ -112,6 +112,6 @@ const WebhooksListPage = observer(() => {
</div> </div>
</SettingsContentWrapper> </SettingsContentWrapper>
); );
}); }
export default WebhooksListPage; export default observer(WebhooksListPage);

View file

@ -2,29 +2,34 @@
import { useState } from "react"; import { useState } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useTheme } from "next-themes";
import { useTranslation } from "@plane/i18n"; import { useTranslation } from "@plane/i18n";
// ui // ui
import { Button } from "@plane/propel/button"; import { Button } from "@plane/propel/button";
// assets
import darkActivityAsset from "@/app/assets/empty-state/profile/activity-dark.webp?url";
import lightActivityAsset from "@/app/assets/empty-state/profile/activity-light.webp?url";
// components // components
import { PageHead } from "@/components/core/page-title"; import { PageHead } from "@/components/core/page-title";
import { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root"; import { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root";
import { ProfileActivityListPage } from "@/components/profile/activity/profile-activity-list"; import { ProfileActivityListPage } from "@/components/profile/activity/profile-activity-list";
// hooks // hooks
import { SettingsHeading } from "@/components/settings/heading"; import { SettingsHeading } from "@/components/settings/heading";
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
const PER_PAGE = 100; const PER_PAGE = 100;
const ProfileActivityPage = observer(() => { function ProfileActivityPage() {
// states // states
const [pageCount, setPageCount] = useState(1); const [pageCount, setPageCount] = useState(1);
const [totalPages, setTotalPages] = useState(0); const [totalPages, setTotalPages] = useState(0);
const [resultsCount, setResultsCount] = useState(0); const [resultsCount, setResultsCount] = useState(0);
const [isEmpty, setIsEmpty] = useState(false); const [isEmpty, setIsEmpty] = useState(false);
// theme hook
const { resolvedTheme } = useTheme();
// plane hooks // plane hooks
const { t } = useTranslation(); const { t } = useTranslation();
// derived values // derived values
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/profile/activity" }); const resolvedPath = resolvedTheme === "light" ? lightActivityAsset : darkActivityAsset;
const updateTotalPages = (count: number) => setTotalPages(count); const updateTotalPages = (count: number) => setTotalPages(count);
@ -84,6 +89,6 @@ const ProfileActivityPage = observer(() => {
)} )}
</> </>
); );
}); }
export default ProfileActivityPage; export default observer(ProfileActivityPage);

View file

@ -21,7 +21,7 @@ import { useWorkspace } from "@/hooks/store/use-workspace";
const apiTokenService = new APITokenService(); const apiTokenService = new APITokenService();
const ApiTokensPage = observer(() => { function ApiTokensPage() {
// states // states
const [isCreateTokenModalOpen, setIsCreateTokenModalOpen] = useState(false); const [isCreateTokenModalOpen, setIsCreateTokenModalOpen] = useState(false);
// router // router
@ -106,6 +106,6 @@ const ApiTokensPage = observer(() => {
</section> </section>
</div> </div>
); );
}); }
export default ApiTokensPage; export default observer(ApiTokensPage);

View file

@ -1,8 +1,8 @@
"use client"; "use client";
import type { ReactNode } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
import { Outlet } from "react-router";
// components // components
import { SettingsContentWrapper } from "@/components/settings/content-wrapper"; import { SettingsContentWrapper } from "@/components/settings/content-wrapper";
import { getProfileActivePath } from "@/components/settings/helper"; import { getProfileActivePath } from "@/components/settings/helper";
@ -10,12 +10,7 @@ import { SettingsMobileNav } from "@/components/settings/mobile";
// local imports // local imports
import { ProfileSidebar } from "./sidebar"; import { ProfileSidebar } from "./sidebar";
type Props = { function ProfileSettingsLayout() {
children: ReactNode;
};
const ProfileSettingsLayout = observer((props: Props) => {
const { children } = props;
// router // router
const pathname = usePathname(); const pathname = usePathname();
@ -27,11 +22,13 @@ const ProfileSettingsLayout = observer((props: Props) => {
<ProfileSidebar /> <ProfileSidebar />
</div> </div>
<div className="w-full h-full overflow-y-scroll md:pt-page-y"> <div className="w-full h-full overflow-y-scroll md:pt-page-y">
<SettingsContentWrapper>{children}</SettingsContentWrapper> <SettingsContentWrapper>
<Outlet />
</SettingsContentWrapper>
</div> </div>
</div> </div>
</> </>
); );
}); }
export default ProfileSettingsLayout; export default observer(ProfileSettingsLayout);

View file

@ -9,7 +9,7 @@ import { ProfileForm } from "@/components/profile/form";
// hooks // hooks
import { useUser } from "@/hooks/store/user"; import { useUser } from "@/hooks/store/user";
const ProfileSettingsPage = observer(() => { function ProfileSettingsPage() {
const { t } = useTranslation(); const { t } = useTranslation();
// store hooks // store hooks
const { data: currentUser, userProfile } = useUser(); const { data: currentUser, userProfile } = useUser();
@ -27,6 +27,6 @@ const ProfileSettingsPage = observer(() => {
<ProfileForm user={currentUser} profile={userProfile.data} /> <ProfileForm user={currentUser} profile={userProfile.data} />
</> </>
); );
}); }
export default ProfileSettingsPage; export default observer(ProfileSettingsPage);

View file

@ -13,7 +13,7 @@ import { SettingsHeading } from "@/components/settings/heading";
// hooks // hooks
import { useUserProfile } from "@/hooks/store/user"; import { useUserProfile } from "@/hooks/store/user";
const ProfileAppearancePage = observer(() => { function ProfileAppearancePage() {
const { t } = useTranslation(); const { t } = useTranslation();
// hooks // hooks
const { data: userProfile } = useUserProfile(); const { data: userProfile } = useUserProfile();
@ -44,6 +44,6 @@ const ProfileAppearancePage = observer(() => {
)} )}
</> </>
); );
}); }
export default ProfileAppearancePage; export default observer(ProfileAppearancePage);

View file

@ -42,7 +42,7 @@ const defaultShowPassword = {
confirmPassword: false, confirmPassword: false,
}; };
const SecurityPage = observer(() => { function SecurityPage() {
// store // store
const { data: currentUser, changePassword } = useUser(); const { data: currentUser, changePassword } = useUser();
// states // states
@ -255,6 +255,6 @@ const SecurityPage = observer(() => {
</form> </form>
</> </>
); );
}); }
export default SecurityPage; export default observer(SecurityPage);

View file

@ -0,0 +1,19 @@
"use client";
import { observer } from "mobx-react";
import { Outlet } from "react-router";
// plane web imports
import { AutomationsListWrapper } from "@/plane-web/components/automations/list/wrapper";
import type { Route } from "./+types/layout";
function AutomationsListLayout({ params }: Route.ComponentProps) {
const { projectId, workspaceSlug } = params;
return (
<AutomationsListWrapper projectId={projectId} workspaceSlug={workspaceSlug}>
<Outlet />
</AutomationsListWrapper>
);
}
export default observer(AutomationsListLayout);

View file

@ -1,8 +1,6 @@
"use client"; "use client";
import React from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import { useTranslation } from "@plane/i18n"; import { useTranslation } from "@plane/i18n";
import { TOAST_TYPE, setToast } from "@plane/propel/toast"; import { TOAST_TYPE, setToast } from "@plane/propel/toast";
@ -19,12 +17,11 @@ import { useProject } from "@/hooks/store/use-project";
import { useUserPermissions } from "@/hooks/store/user"; import { useUserPermissions } from "@/hooks/store/user";
// plane web imports // plane web imports
import { CustomAutomationsRoot } from "@/plane-web/components/automations/root"; import { CustomAutomationsRoot } from "@/plane-web/components/automations/root";
import type { Route } from "./+types/page";
const AutomationSettingsPage = observer(() => { function AutomationSettingsPage({ params }: Route.ComponentProps) {
// router // router
const { workspaceSlug: workspaceSlugParam, projectId: projectIdParam } = useParams(); const { workspaceSlug, projectId } = params;
const workspaceSlug = workspaceSlugParam?.toString();
const projectId = projectIdParam?.toString();
// store hooks // store hooks
const { workspaceUserInfo, allowPermissions } = useUserPermissions(); const { workspaceUserInfo, allowPermissions } = useUserPermissions();
const { currentProjectDetails: projectDetails, updateProject } = useProject(); const { currentProjectDetails: projectDetails, updateProject } = useProject();
@ -35,15 +32,17 @@ const AutomationSettingsPage = observer(() => {
const canPerformProjectAdminActions = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT); const canPerformProjectAdminActions = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT);
const handleChange = async (formData: Partial<IProject>) => { const handleChange = async (formData: Partial<IProject>) => {
if (!workspaceSlug || !projectId || !projectDetails) return; if (!projectDetails) return;
await updateProject(workspaceSlug.toString(), projectId.toString(), formData).catch(() => { try {
await updateProject(workspaceSlug, projectId, formData);
} catch {
setToast({ setToast({
type: TOAST_TYPE.ERROR, type: TOAST_TYPE.ERROR,
title: "Error!", title: "Error!",
message: "Something went wrong. Please try again.", message: "Something went wrong. Please try again.",
}); });
}); }
}; };
// derived values // derived values
@ -67,6 +66,6 @@ const AutomationSettingsPage = observer(() => {
<CustomAutomationsRoot projectId={projectId} workspaceSlug={workspaceSlug} /> <CustomAutomationsRoot projectId={projectId} workspaceSlug={workspaceSlug} />
</SettingsContentWrapper> </SettingsContentWrapper>
); );
}); }
export default AutomationSettingsPage; export default observer(AutomationSettingsPage);

View file

@ -1,7 +1,6 @@
"use client"; "use client";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// components // components
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import { NotAuthorizedView } from "@/components/auth-screens/not-authorized-view"; import { NotAuthorizedView } from "@/components/auth-screens/not-authorized-view";
@ -11,9 +10,10 @@ import { EstimateRoot } from "@/components/estimates";
import { SettingsContentWrapper } from "@/components/settings/content-wrapper"; import { SettingsContentWrapper } from "@/components/settings/content-wrapper";
import { useProject } from "@/hooks/store/use-project"; import { useProject } from "@/hooks/store/use-project";
import { useUserPermissions } from "@/hooks/store/user"; import { useUserPermissions } from "@/hooks/store/user";
import type { Route } from "./+types/page";
const EstimatesSettingsPage = observer(() => { function EstimatesSettingsPage({ params }: Route.ComponentProps) {
const { workspaceSlug, projectId } = useParams(); const { workspaceSlug, projectId } = params;
// store // store
const { currentProjectDetails } = useProject(); const { currentProjectDetails } = useProject();
const { workspaceUserInfo, allowPermissions } = useUserPermissions(); const { workspaceUserInfo, allowPermissions } = useUserPermissions();
@ -22,8 +22,6 @@ const EstimatesSettingsPage = observer(() => {
const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - Estimates` : undefined; const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - Estimates` : undefined;
const canPerformProjectAdminActions = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT); const canPerformProjectAdminActions = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT);
if (!workspaceSlug || !projectId) return <></>;
if (workspaceUserInfo && !canPerformProjectAdminActions) { if (workspaceUserInfo && !canPerformProjectAdminActions) {
return <NotAuthorizedView section="settings" isProjectView className="h-auto" />; return <NotAuthorizedView section="settings" isProjectView className="h-auto" />;
} }
@ -32,14 +30,10 @@ const EstimatesSettingsPage = observer(() => {
<SettingsContentWrapper> <SettingsContentWrapper>
<PageHead title={pageTitle} /> <PageHead title={pageTitle} />
<div className={`w-full ${canPerformProjectAdminActions ? "" : "pointer-events-none opacity-60"}`}> <div className={`w-full ${canPerformProjectAdminActions ? "" : "pointer-events-none opacity-60"}`}>
<EstimateRoot <EstimateRoot workspaceSlug={workspaceSlug} projectId={projectId} isAdmin={canPerformProjectAdminActions} />
workspaceSlug={workspaceSlug?.toString()}
projectId={projectId?.toString()}
isAdmin={canPerformProjectAdminActions}
/>
</div> </div>
</SettingsContentWrapper> </SettingsContentWrapper>
); );
}); }
export default EstimatesSettingsPage; export default observer(EstimatesSettingsPage);

View file

@ -1,7 +1,6 @@
"use client"; "use client";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// components // components
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import { NotAuthorizedView } from "@/components/auth-screens/not-authorized-view"; import { NotAuthorizedView } from "@/components/auth-screens/not-authorized-view";
@ -11,9 +10,10 @@ import { SettingsContentWrapper } from "@/components/settings/content-wrapper";
import { useProject } from "@/hooks/store/use-project"; import { useProject } from "@/hooks/store/use-project";
import { useUserPermissions } from "@/hooks/store/user"; import { useUserPermissions } from "@/hooks/store/user";
import { ProjectFeaturesList } from "@/plane-web/components/projects/settings/features-list"; import { ProjectFeaturesList } from "@/plane-web/components/projects/settings/features-list";
import type { Route } from "./+types/page";
const FeaturesSettingsPage = observer(() => { function FeaturesSettingsPage({ params }: Route.ComponentProps) {
const { workspaceSlug, projectId } = useParams(); const { workspaceSlug, projectId } = params;
// store // store
const { workspaceUserInfo, allowPermissions } = useUserPermissions(); const { workspaceUserInfo, allowPermissions } = useUserPermissions();
@ -22,8 +22,6 @@ const FeaturesSettingsPage = observer(() => {
const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - Features` : undefined; const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - Features` : undefined;
const canPerformProjectAdminActions = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT); const canPerformProjectAdminActions = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT);
if (!workspaceSlug || !projectId) return null;
if (workspaceUserInfo && !canPerformProjectAdminActions) { if (workspaceUserInfo && !canPerformProjectAdminActions) {
return <NotAuthorizedView section="settings" isProjectView className="h-auto" />; return <NotAuthorizedView section="settings" isProjectView className="h-auto" />;
} }
@ -33,13 +31,13 @@ const FeaturesSettingsPage = observer(() => {
<PageHead title={pageTitle} /> <PageHead title={pageTitle} />
<section className={`w-full ${canPerformProjectAdminActions ? "" : "opacity-60"}`}> <section className={`w-full ${canPerformProjectAdminActions ? "" : "opacity-60"}`}>
<ProjectFeaturesList <ProjectFeaturesList
workspaceSlug={workspaceSlug.toString()} workspaceSlug={workspaceSlug}
projectId={projectId.toString()} projectId={projectId}
isAdmin={canPerformProjectAdminActions} isAdmin={canPerformProjectAdminActions}
/> />
</section> </section>
</SettingsContentWrapper> </SettingsContentWrapper>
); );
}); }
export default FeaturesSettingsPage; export default observer(FeaturesSettingsPage);

View file

@ -14,7 +14,7 @@ import { SettingsContentWrapper } from "@/components/settings/content-wrapper";
import { useProject } from "@/hooks/store/use-project"; import { useProject } from "@/hooks/store/use-project";
import { useUserPermissions } from "@/hooks/store/user"; import { useUserPermissions } from "@/hooks/store/user";
const LabelsSettingsPage = observer(() => { function LabelsSettingsPage() {
// store hooks // store hooks
const { currentProjectDetails } = useProject(); const { currentProjectDetails } = useProject();
const { workspaceUserInfo, allowPermissions } = useUserPermissions(); const { workspaceUserInfo, allowPermissions } = useUserPermissions();
@ -54,6 +54,6 @@ const LabelsSettingsPage = observer(() => {
</div> </div>
</SettingsContentWrapper> </SettingsContentWrapper>
); );
}); }
export default LabelsSettingsPage; export default observer(LabelsSettingsPage);

View file

@ -1,7 +1,6 @@
"use client"; "use client";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// plane imports // plane imports
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import { useTranslation } from "@plane/i18n"; import { useTranslation } from "@plane/i18n";
@ -18,18 +17,17 @@ import { useUserPermissions } from "@/hooks/store/user";
// plane web imports // plane web imports
import { ProjectTeamspaceList } from "@/plane-web/components/projects/teamspaces/teamspace-list"; import { ProjectTeamspaceList } from "@/plane-web/components/projects/teamspaces/teamspace-list";
import { getProjectSettingsPageLabelI18nKey } from "@/plane-web/helpers/project-settings"; import { getProjectSettingsPageLabelI18nKey } from "@/plane-web/helpers/project-settings";
import type { Route } from "./+types/page";
const MembersSettingsPage = observer(() => { function MembersSettingsPage({ params }: Route.ComponentProps) {
// router // router
const { workspaceSlug: routerWorkspaceSlug, projectId: routerProjectId } = useParams(); const { workspaceSlug, projectId } = params;
// plane hooks // plane hooks
const { t } = useTranslation(); const { t } = useTranslation();
// store hooks // store hooks
const { currentProjectDetails } = useProject(); const { currentProjectDetails } = useProject();
const { workspaceUserInfo, allowPermissions } = useUserPermissions(); const { workspaceUserInfo, allowPermissions } = useUserPermissions();
// derived values // derived values
const projectId = routerProjectId?.toString();
const workspaceSlug = routerWorkspaceSlug?.toString();
const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - Members` : undefined; const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - Members` : undefined;
const isProjectMemberOrAdmin = allowPermissions( const isProjectMemberOrAdmin = allowPermissions(
[EUserPermissions.ADMIN, EUserPermissions.MEMBER], [EUserPermissions.ADMIN, EUserPermissions.MEMBER],
@ -51,6 +49,6 @@ const MembersSettingsPage = observer(() => {
<ProjectMemberList projectId={projectId} workspaceSlug={workspaceSlug} /> <ProjectMemberList projectId={projectId} workspaceSlug={workspaceSlug} />
</SettingsContentWrapper> </SettingsContentWrapper>
); );
}); }
export default MembersSettingsPage; export default observer(MembersSettingsPage);

View file

@ -2,7 +2,6 @@
import { useState } from "react"; import { useState } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import useSWR from "swr"; import useSWR from "swr";
// plane imports // plane imports
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
@ -18,41 +17,34 @@ import { SettingsContentWrapper } from "@/components/settings/content-wrapper";
// hooks // hooks
import { useProject } from "@/hooks/store/use-project"; import { useProject } from "@/hooks/store/use-project";
import { useUserPermissions } from "@/hooks/store/user"; import { useUserPermissions } from "@/hooks/store/user";
import type { Route } from "./+types/page";
const ProjectSettingsPage = observer(() => { function ProjectSettingsPage({ params }: Route.ComponentProps) {
// states // states
const [selectProject, setSelectedProject] = useState<string | null>(null); const [selectProject, setSelectedProject] = useState<string | null>(null);
const [archiveProject, setArchiveProject] = useState<boolean>(false); const [archiveProject, setArchiveProject] = useState<boolean>(false);
// router // router
const { workspaceSlug, projectId } = useParams(); const { workspaceSlug, projectId } = params;
// store hooks // store hooks
const { currentProjectDetails, fetchProjectDetails } = useProject(); const { currentProjectDetails, fetchProjectDetails } = useProject();
const { allowPermissions } = useUserPermissions(); const { allowPermissions } = useUserPermissions();
// api call to fetch project details // api call to fetch project details
// TODO: removed this API if not necessary // TODO: removed this API if not necessary
const { isLoading } = useSWR( const { isLoading } = useSWR(`PROJECT_DETAILS_${projectId}`, () => fetchProjectDetails(workspaceSlug, projectId));
workspaceSlug && projectId ? `PROJECT_DETAILS_${projectId}` : null,
workspaceSlug && projectId ? () => fetchProjectDetails(workspaceSlug.toString(), projectId.toString()) : null
);
// derived values // derived values
const isAdmin = allowPermissions( const isAdmin = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT, workspaceSlug, projectId);
[EUserPermissions.ADMIN],
EUserPermissionsLevel.PROJECT,
workspaceSlug.toString(),
projectId.toString()
);
const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - General Settings` : undefined; const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - General Settings` : undefined;
return ( return (
<SettingsContentWrapper> <SettingsContentWrapper>
<PageHead title={pageTitle} /> <PageHead title={pageTitle} />
{currentProjectDetails && workspaceSlug && projectId && ( {currentProjectDetails && (
<> <>
<ArchiveRestoreProjectModal <ArchiveRestoreProjectModal
workspaceSlug={workspaceSlug.toString()} workspaceSlug={workspaceSlug}
projectId={projectId.toString()} projectId={projectId}
isOpen={archiveProject} isOpen={archiveProject}
onClose={() => setArchiveProject(false)} onClose={() => setArchiveProject(false)}
archive archive
@ -66,11 +58,11 @@ const ProjectSettingsPage = observer(() => {
)} )}
<div className={`w-full ${isAdmin ? "" : "opacity-60"}`}> <div className={`w-full ${isAdmin ? "" : "opacity-60"}`}>
{currentProjectDetails && workspaceSlug && projectId && !isLoading ? ( {currentProjectDetails && !isLoading ? (
<ProjectDetailsForm <ProjectDetailsForm
project={currentProjectDetails} project={currentProjectDetails}
workspaceSlug={workspaceSlug.toString()} workspaceSlug={workspaceSlug}
projectId={projectId.toString()} projectId={projectId}
isAdmin={isAdmin} isAdmin={isAdmin}
/> />
) : ( ) : (
@ -92,6 +84,6 @@ const ProjectSettingsPage = observer(() => {
</div> </div>
</SettingsContentWrapper> </SettingsContentWrapper>
); );
}); }
export default ProjectSettingsPage; export default observer(ProjectSettingsPage);

View file

@ -1,7 +1,6 @@
"use client"; "use client";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import { useTranslation } from "@plane/i18n"; import { useTranslation } from "@plane/i18n";
// components // components
@ -13,9 +12,10 @@ import { SettingsContentWrapper } from "@/components/settings/content-wrapper";
import { SettingsHeading } from "@/components/settings/heading"; import { SettingsHeading } from "@/components/settings/heading";
import { useProject } from "@/hooks/store/use-project"; import { useProject } from "@/hooks/store/use-project";
import { useUserPermissions } from "@/hooks/store/user"; import { useUserPermissions } from "@/hooks/store/user";
import type { Route } from "./+types/page";
const StatesSettingsPage = observer(() => { function StatesSettingsPage({ params }: Route.ComponentProps) {
const { workspaceSlug, projectId } = useParams(); const { workspaceSlug, projectId } = params;
// store // store
const { currentProjectDetails } = useProject(); const { currentProjectDetails } = useProject();
const { workspaceUserInfo, allowPermissions } = useUserPermissions(); const { workspaceUserInfo, allowPermissions } = useUserPermissions();
@ -42,12 +42,10 @@ const StatesSettingsPage = observer(() => {
title={t("project_settings.states.heading")} title={t("project_settings.states.heading")}
description={t("project_settings.states.description")} description={t("project_settings.states.description")}
/> />
{workspaceSlug && projectId && ( <ProjectStateRoot workspaceSlug={workspaceSlug} projectId={projectId} />
<ProjectStateRoot workspaceSlug={workspaceSlug.toString()} projectId={projectId.toString()} />
)}
</div> </div>
</SettingsContentWrapper> </SettingsContentWrapper>
); );
}); }
export default StatesSettingsPage; export default observer(StatesSettingsPage);

View file

@ -1,9 +1,9 @@
"use client"; "use client";
import type { ReactNode } from "react";
import { useEffect } from "react"; import { useEffect } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useParams, usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
import { Outlet } from "react-router";
// components // components
import { getProjectActivePath } from "@/components/settings/helper"; import { getProjectActivePath } from "@/components/settings/helper";
import { SettingsMobileNav } from "@/components/settings/mobile"; import { SettingsMobileNav } from "@/components/settings/mobile";
@ -11,17 +11,13 @@ import { ProjectSettingsSidebar } from "@/components/settings/project/sidebar";
import { useProject } from "@/hooks/store/use-project"; import { useProject } from "@/hooks/store/use-project";
import { useAppRouter } from "@/hooks/use-app-router"; import { useAppRouter } from "@/hooks/use-app-router";
import { ProjectAuthWrapper } from "@/plane-web/layouts/project-wrapper"; import { ProjectAuthWrapper } from "@/plane-web/layouts/project-wrapper";
import type { Route } from "./+types/layout";
type Props = { function ProjectSettingsLayout({ params }: Route.ComponentProps) {
children: ReactNode;
};
const ProjectSettingsLayout = observer((props: Props) => {
const { children } = props;
// router // router
const router = useAppRouter(); const router = useAppRouter();
const pathname = usePathname(); const pathname = usePathname();
const { workspaceSlug, projectId } = useParams(); const { workspaceSlug, projectId } = params;
const { joinedProjectIds } = useProject(); const { joinedProjectIds } = useProject();
useEffect(() => { useEffect(() => {
@ -34,14 +30,16 @@ const ProjectSettingsLayout = observer((props: Props) => {
return ( return (
<> <>
<SettingsMobileNav hamburgerContent={ProjectSettingsSidebar} activePath={getProjectActivePath(pathname) || ""} /> <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="relative flex h-full w-full">
<div className="hidden md:block">{projectId && <ProjectSettingsSidebar />}</div> <div className="hidden md:block">{projectId && <ProjectSettingsSidebar />}</div>
<div className="w-full h-full overflow-y-scroll md:pt-page-y">{children}</div> <div className="w-full h-full overflow-y-scroll md:pt-page-y">
<Outlet />
</div>
</div> </div>
</ProjectAuthWrapper> </ProjectAuthWrapper>
</> </>
); );
}); }
export default ProjectSettingsLayout; export default observer(ProjectSettingsLayout);

View file

@ -1,12 +1,15 @@
"use client"; "use client";
import { Outlet } from "react-router";
import { AppRailProvider } from "@/hooks/context/app-rail-context"; import { AppRailProvider } from "@/hooks/context/app-rail-context";
import { WorkspaceContentWrapper } from "@/plane-web/components/workspace/content-wrapper"; import { WorkspaceContentWrapper } from "@/plane-web/components/workspace/content-wrapper";
export default function WorkspaceLayout({ children }: { children: React.ReactNode }) { export default function WorkspaceLayout() {
return ( return (
<AppRailProvider> <AppRailProvider>
<WorkspaceContentWrapper>{children}</WorkspaceContentWrapper> <WorkspaceContentWrapper>
<Outlet />
</WorkspaceContentWrapper>
</AppRailProvider> </AppRailProvider>
); );
} }

View file

@ -1,9 +1,8 @@
import type { Metadata } from "next"; import { Outlet } from "react-router";
import type { Route } from "./+types/layout";
export const metadata: Metadata = { export default function ForgotPasswordLayout() {
title: "Forgot Password - Plane", return <Outlet />;
};
export default function ForgotPasswordLayout({ children }: { children: React.ReactNode }) {
return children;
} }
export const meta: Route.MetaFunction = () => [{ title: "Forgot Password - Plane" }];

View file

@ -10,7 +10,8 @@ import { EAuthModes, EPageTypes } from "@/helpers/authentication.helper";
import DefaultLayout from "@/layouts/default-layout"; import DefaultLayout from "@/layouts/default-layout";
import { AuthenticationWrapper } from "@/lib/wrappers/authentication-wrapper"; import { AuthenticationWrapper } from "@/lib/wrappers/authentication-wrapper";
const ForgotPasswordPage = observer(() => ( function ForgotPasswordPage() {
return (
<DefaultLayout> <DefaultLayout>
<AuthenticationWrapper pageType={EPageTypes.NON_AUTHENTICATED}> <AuthenticationWrapper pageType={EPageTypes.NON_AUTHENTICATED}>
<div className="relative z-10 flex flex-col items-center w-screen h-screen overflow-hidden overflow-y-auto pt-6 pb-10 px-8"> <div className="relative z-10 flex flex-col items-center w-screen h-screen overflow-hidden overflow-y-auto pt-6 pb-10 px-8">
@ -19,6 +20,7 @@ const ForgotPasswordPage = observer(() => (
</div> </div>
</AuthenticationWrapper> </AuthenticationWrapper>
</DefaultLayout> </DefaultLayout>
)); );
}
export default ForgotPasswordPage; export default observer(ForgotPasswordPage);

View file

@ -1,9 +1,8 @@
import type { Metadata } from "next"; import { Outlet } from "react-router";
import type { Route } from "./+types/layout";
export const metadata: Metadata = { export default function ResetPasswordLayout() {
title: "Reset Password - Plane", return <Outlet />;
};
export default function ResetPasswordLayout({ children }: { children: React.ReactNode }) {
return children;
} }
export const meta: Route.MetaFunction = () => [{ title: "Reset Password - Plane" }];

View file

@ -1,9 +1,8 @@
import type { Metadata } from "next"; import { Outlet } from "react-router";
import type { Route } from "./+types/layout";
export const metadata: Metadata = { export default function SetPasswordLayout() {
title: "Set Password - Plane", return <Outlet />;
};
export default function SetPasswordLayout({ children }: { children: React.ReactNode }) {
return children;
} }
export const meta: Route.MetaFunction = () => [{ title: "Set Password - Plane" }];

View file

@ -1,9 +1,8 @@
import type { Metadata } from "next"; import { Outlet } from "react-router";
import type { Route } from "./+types/layout";
export const metadata: Metadata = { export default function CreateWorkspaceLayout() {
title: "Create Workspace", return <Outlet />;
};
export default function CreateWorkspaceLayout({ children }: { children: React.ReactNode }) {
return children;
} }
export const meta: Route.MetaFunction = () => [{ title: "Create Workspace" }];

View file

@ -9,6 +9,8 @@ import { useTranslation } from "@plane/i18n";
import { Button, getButtonStyling } from "@plane/propel/button"; import { Button, getButtonStyling } from "@plane/propel/button";
import { PlaneLogo } from "@plane/propel/icons"; import { PlaneLogo } from "@plane/propel/icons";
import type { IWorkspace } from "@plane/types"; import type { IWorkspace } from "@plane/types";
// assets
import WorkspaceCreationDisabled from "@/app/assets/workspace/workspace-creation-disabled.png?url";
// components // components
import { CreateWorkspaceForm } from "@/components/workspace/create-workspace-form"; import { CreateWorkspaceForm } from "@/components/workspace/create-workspace-form";
// hooks // hooks
@ -18,10 +20,8 @@ import { useAppRouter } from "@/hooks/use-app-router";
import { AuthenticationWrapper } from "@/lib/wrappers/authentication-wrapper"; import { AuthenticationWrapper } from "@/lib/wrappers/authentication-wrapper";
// plane web helpers // plane web helpers
import { getIsWorkspaceCreationDisabled } from "@/plane-web/helpers/instance.helper"; import { getIsWorkspaceCreationDisabled } from "@/plane-web/helpers/instance.helper";
// images
import WorkspaceCreationDisabled from "@/public/workspace/workspace-creation-disabled.png";
const CreateWorkspacePage = observer(() => { function CreateWorkspacePage() {
const { t } = useTranslation(); const { t } = useTranslation();
// router // router
const router = useAppRouter(); const router = useAppRouter();
@ -103,6 +103,6 @@ const CreateWorkspacePage = observer(() => {
</div> </div>
</AuthenticationWrapper> </AuthenticationWrapper>
); );
}); }
export default CreateWorkspacePage; export default observer(CreateWorkspacePage);

View file

@ -1,3 +1,8 @@
export default function InstallationProviderLayout({ children }: { children: React.ReactNode }) { import { Outlet } from "react-router";
return children; import type { Route } from "./+types/layout";
export default function InstallationProviderLayout() {
return <Outlet />;
} }
export const meta: Route.MetaFunction = () => [{ title: "Installations" }];

View file

@ -1,18 +1,19 @@
"use client"; "use client";
import React, { useEffect } from "react"; import { useEffect } from "react";
import { useParams, useSearchParams } from "next/navigation"; import { useSearchParams } from "next/navigation";
// ui // ui
import { LogoSpinner } from "@/components/common/logo-spinner"; import { LogoSpinner } from "@/components/common/logo-spinner";
// services // services
import { AppInstallationService } from "@/services/app_installation.service"; import { AppInstallationService } from "@/services/app_installation.service";
import type { Route } from "./+types/page";
// services // services
const appInstallationService = new AppInstallationService(); const appInstallationService = new AppInstallationService();
export default function AppPostInstallation() { export default function AppPostInstallation({ params }: Route.ComponentProps) {
// params // params
const { provider } = useParams(); const { provider } = params;
// query params // query params
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const installation_id = searchParams.get("installation_id"); const installation_id = searchParams.get("installation_id");

View file

@ -1,9 +1,8 @@
import type { Metadata } from "next"; import { Outlet } from "react-router";
import type { Route } from "./+types/layout";
export const metadata: Metadata = { export default function InvitationsLayout() {
title: "Invitations", return <Outlet />;
};
export default function InvitationsLayout({ children }: { children: React.ReactNode }) {
return children;
} }
export const meta: Route.MetaFunction = () => [{ title: "Invitations" }];

View file

@ -15,6 +15,8 @@ import { PlaneLogo } from "@plane/propel/icons";
import { TOAST_TYPE, setToast } from "@plane/propel/toast"; import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import type { IWorkspaceMemberInvitation } from "@plane/types"; import type { IWorkspaceMemberInvitation } from "@plane/types";
import { truncateText } from "@plane/utils"; import { truncateText } from "@plane/utils";
// assets
import emptyInvitation from "@/app/assets/empty-state/invitation.svg?url";
// components // components
import { EmptyState } from "@/components/common/empty-state"; import { EmptyState } from "@/components/common/empty-state";
import { WorkspaceLogo } from "@/components/workspace/logo"; import { WorkspaceLogo } from "@/components/workspace/logo";
@ -29,12 +31,10 @@ import { useAppRouter } from "@/hooks/use-app-router";
import { AuthenticationWrapper } from "@/lib/wrappers/authentication-wrapper"; import { AuthenticationWrapper } from "@/lib/wrappers/authentication-wrapper";
// plane web services // plane web services
import { WorkspaceService } from "@/plane-web/services"; import { WorkspaceService } from "@/plane-web/services";
// images
import emptyInvitation from "@/public/empty-state/invitation.svg";
const workspaceService = new WorkspaceService(); const workspaceService = new WorkspaceService();
const UserInvitationsPage = observer(() => { function UserInvitationsPage() {
// states // states
const [invitationsRespond, setInvitationsRespond] = useState<string[]>([]); const [invitationsRespond, setInvitationsRespond] = useState<string[]>([]);
const [isJoiningWorkspaces, setIsJoiningWorkspaces] = useState(false); const [isJoiningWorkspaces, setIsJoiningWorkspaces] = useState(false);
@ -220,6 +220,6 @@ const UserInvitationsPage = observer(() => {
</div> </div>
</AuthenticationWrapper> </AuthenticationWrapper>
); );
}); }
export default UserInvitationsPage; export default observer(UserInvitationsPage);

View file

@ -1,28 +1,25 @@
"use client"; "use client";
import { useEffect } from "react"; // TODO: Check if we need this
import ReactDOM from "react-dom";
// https://nextjs.org/docs/app/api-reference/functions/generate-metadata#link-relpreload // https://nextjs.org/docs/app/api-reference/functions/generate-metadata#link-relpreload
export const usePreloadResources = () => { // export const usePreloadResources = () => {
useEffect(() => { // useEffect(() => {
const preloadItem = (url: string) => { // const preloadItem = (url: string) => {
ReactDOM.preload(url, { as: "fetch", crossOrigin: "use-credentials" }); // ReactDOM.preload(url, { as: "fetch", crossOrigin: "use-credentials" });
}; // };
const urls = [ // const urls = [
`${process.env.NEXT_PUBLIC_API_BASE_URL}/api/instances/`, // `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/instances/`,
`${process.env.NEXT_PUBLIC_API_BASE_URL}/api/users/me/`, // `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/users/me/`,
`${process.env.NEXT_PUBLIC_API_BASE_URL}/api/users/me/profile/`, // `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/users/me/profile/`,
`${process.env.NEXT_PUBLIC_API_BASE_URL}/api/users/me/settings/`, // `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/users/me/settings/`,
`${process.env.NEXT_PUBLIC_API_BASE_URL}/api/users/me/workspaces/?v=${Date.now()}`, // `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/users/me/workspaces/?v=${Date.now()}`,
]; // ];
urls.forEach((url) => preloadItem(url)); // urls.forEach((url) => preloadItem(url));
}, []); // }, []);
}; // };
export const PreloadResources = () => { export const PreloadResources = () =>
usePreloadResources(); // usePreloadResources();
return null; null;
};

View file

@ -1,31 +1,23 @@
import type { Metadata, Viewport } from "next"; import { Outlet } from "react-router";
import type { Route } from "./+types/layout";
import { PreloadResources } from "./layout.preload"; import { PreloadResources } from "./layout.preload";
// types
// styles // styles
import "@/styles/power-k.css"; import "@/styles/power-k.css";
import "@/styles/emoji.css"; import "@/styles/emoji.css";
import "@plane/propel/styles/react-day-picker.css"; import "@plane/propel/styles/react-day-picker.css";
export const metadata: Metadata = { export const meta: Route.MetaFunction = () => [
robots: { { name: "robots", content: "noindex, nofollow" },
index: false, { name: "viewport", content: "width=device-width, initial-scale=1, minimum-scale=1, viewport-fit=cover" },
follow: false, ];
},
};
export const viewport: Viewport = { export default function AppLayout() {
minimumScale: 1,
initialScale: 1,
width: "device-width",
viewportFit: "cover",
};
export default function AppLayout({ children }: { children: React.ReactNode }) {
return ( return (
<> <>
<PreloadResources /> <PreloadResources />
{children} <Outlet />
</> </>
); );
} }

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