mirror of
https://github.com/makeplane/plane.git
synced 2026-01-06 05:40:41 -06:00
[WEB-5043] feat: web vite migration (#7973)
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { Links, Meta, Outlet, Scripts } 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 favicon16 from "@/app/assets/favicon/favicon-16x16.png?url";
|
||||
import favicon32 from "@/app/assets/favicon/favicon-32x32.png?url";
|
||||
import faviconIco from "@/app/assets/favicon/favicon.ico?url";
|
||||
import globalStyles from "@/styles/globals.css?url";
|
||||
import type { Route } from "./+types/root";
|
||||
import { AppProviders } from "./providers";
|
||||
|
||||
@@ -19,6 +19,7 @@ export const links: LinksFunction = () => [
|
||||
{ rel: "icon", type: "image/png", sizes: "16x16", href: favicon16 },
|
||||
{ rel: "shortcut icon", href: faviconIco },
|
||||
{ rel: "manifest", href: `/site.webmanifest.json` },
|
||||
{ rel: "stylesheet", href: globalStyles },
|
||||
];
|
||||
|
||||
export function Layout({ children }: { children: ReactNode }) {
|
||||
@@ -56,6 +57,10 @@ export default function Root() {
|
||||
return <Outlet />;
|
||||
}
|
||||
|
||||
export function HydrateFallback() {
|
||||
return null;
|
||||
}
|
||||
|
||||
export function ErrorBoundary() {
|
||||
return (
|
||||
<div>
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
"use client";
|
||||
|
||||
import { redirect } from "react-router";
|
||||
import type { ClientLoaderFunctionArgs } from "react-router";
|
||||
// plane imports
|
||||
import { SitesProjectPublishService } from "@plane/services";
|
||||
import type { TProjectPublishSettings } from "@plane/types";
|
||||
// components
|
||||
import { LogoSpinner } from "@/components/common/logo-spinner";
|
||||
import type { Route } from "./+types/page";
|
||||
|
||||
const publishService = new SitesProjectPublishService();
|
||||
|
||||
export const clientLoader = async ({ params, request }: ClientLoaderFunctionArgs) => {
|
||||
export const clientLoader = async ({ params, request }: Route.ClientLoaderArgs) => {
|
||||
const { workspaceSlug, projectId } = params;
|
||||
|
||||
// Validate required params
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { Links, Meta, Outlet, Scripts } from "react-router";
|
||||
import type { LinksFunction } from "react-router";
|
||||
// styles
|
||||
import "@/styles/globals.css";
|
||||
// assets
|
||||
import appleTouchIcon from "@/app/assets/favicon/apple-touch-icon.png?url";
|
||||
import favicon16 from "@/app/assets/favicon/favicon-16x16.png?url";
|
||||
import favicon32 from "@/app/assets/favicon/favicon-32x32.png?url";
|
||||
import faviconIco from "@/app/assets/favicon/favicon.ico?url";
|
||||
import globalStyles from "@/styles/globals.css?url";
|
||||
// types
|
||||
import type { Route } from "./+types/root";
|
||||
// local imports
|
||||
@@ -22,6 +21,7 @@ export const links: LinksFunction = () => [
|
||||
{ rel: "icon", type: "image/png", sizes: "16x16", href: favicon16 },
|
||||
{ rel: "shortcut icon", href: faviconIco },
|
||||
{ rel: "manifest", href: `/site.webmanifest.json` },
|
||||
{ rel: "stylesheet", href: globalStyles },
|
||||
];
|
||||
|
||||
export function Layout({ children }: { children: React.ReactNode }) {
|
||||
@@ -61,6 +61,10 @@ export default function Root() {
|
||||
return <Outlet />;
|
||||
}
|
||||
|
||||
export function HydrateFallback() {
|
||||
return null;
|
||||
}
|
||||
|
||||
export function ErrorBoundary() {
|
||||
return <ErrorPage />;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"@bprogress/core": "catalog:",
|
||||
"@emotion/react": "^11.11.1",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@headlessui/react": "^1.7.13",
|
||||
"@headlessui/react": "^1.7.19",
|
||||
"@plane/constants": "workspace:*",
|
||||
"@plane/editor": "workspace:*",
|
||||
"@plane/i18n": "workspace:*",
|
||||
|
||||
11
apps/web/.dockerignore
Normal file
11
apps/web/.dockerignore
Normal file
@@ -0,0 +1,11 @@
|
||||
node_modules
|
||||
.next
|
||||
.react-router
|
||||
.vite
|
||||
.turbo
|
||||
build
|
||||
dist
|
||||
*.log
|
||||
.env*
|
||||
!.env.example
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
.next/*
|
||||
.react-router/*
|
||||
.vite/*
|
||||
build/*
|
||||
out/*
|
||||
public/*
|
||||
core/local-db/worker/wa-sqlite/src/*
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ["@plane/eslint-config/next.js"],
|
||||
ignorePatterns: ["build/**", "dist/**", ".vite/**"],
|
||||
rules: {
|
||||
"no-duplicate-imports": "off",
|
||||
"import/no-duplicates": ["error", { "prefer-inline": false }],
|
||||
@@ -1,5 +1,9 @@
|
||||
.next
|
||||
.react-router
|
||||
.vite
|
||||
.turbo
|
||||
out/
|
||||
dist/
|
||||
build/
|
||||
build/
|
||||
node_modules
|
||||
pnpm-lock.yaml
|
||||
|
||||
@@ -71,50 +71,16 @@ ENV TURBO_TELEMETRY_DISABLED=1
|
||||
RUN pnpm turbo run build --filter=web
|
||||
|
||||
# *****************************************************************************
|
||||
# STAGE 3: Copy the project and start it
|
||||
# STAGE 3: Serve with nginx
|
||||
# *****************************************************************************
|
||||
FROM base AS runner
|
||||
WORKDIR /app
|
||||
FROM nginx:1.27-alpine AS production
|
||||
|
||||
# Don't run production as root
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
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
|
||||
COPY apps/web/nginx/nginx.conf /etc/nginx/nginx.conf
|
||||
COPY --from=installer /app/apps/web/build/client /usr/share/nginx/html
|
||||
|
||||
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;"]
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
"use client";
|
||||
|
||||
// components
|
||||
import { Outlet } from "react-router";
|
||||
import { AppHeader } from "@/components/core/app-header";
|
||||
import { ContentWrapper } from "@/components/core/content-wrapper";
|
||||
// local imports
|
||||
import { WorkspaceActiveCycleHeader } from "./header";
|
||||
|
||||
export default function WorkspaceActiveCycleLayout({ children }: { children: React.ReactNode }) {
|
||||
export default function WorkspaceActiveCycleLayout() {
|
||||
return (
|
||||
<>
|
||||
<AppHeader header={<WorkspaceActiveCycleHeader />} />
|
||||
<ContentWrapper>{children}</ContentWrapper>
|
||||
<ContentWrapper>
|
||||
<Outlet />
|
||||
</ContentWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useWorkspace } from "@/hooks/store/use-workspace";
|
||||
// plane web components
|
||||
import { WorkspaceActiveCyclesRoot } from "@/plane-web/components/active-cycles";
|
||||
|
||||
const WorkspaceActiveCyclesPage = observer(() => {
|
||||
function WorkspaceActiveCyclesPage() {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
// derived values
|
||||
const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Active Cycles` : undefined;
|
||||
@@ -19,6 +19,6 @@ const WorkspaceActiveCyclesPage = observer(() => {
|
||||
<WorkspaceActiveCyclesRoot />
|
||||
</>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default WorkspaceActiveCyclesPage;
|
||||
export default observer(WorkspaceActiveCyclesPage);
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
"use client";
|
||||
// components
|
||||
import { Outlet } from "react-router";
|
||||
import { AppHeader } from "@/components/core/app-header";
|
||||
import { ContentWrapper } from "@/components/core/content-wrapper";
|
||||
import { WorkspaceAnalyticsHeader } from "./header";
|
||||
|
||||
export default function WorkspaceAnalyticsTabLayout({ children }: { children: React.ReactNode }) {
|
||||
export default function WorkspaceAnalyticsTabLayout() {
|
||||
return (
|
||||
<>
|
||||
<AppHeader header={<WorkspaceAnalyticsHeader />} />
|
||||
<ContentWrapper>{children}</ContentWrapper>
|
||||
<ContentWrapper>
|
||||
<Outlet />
|
||||
</ContentWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,17 +19,9 @@ import { useProject } from "@/hooks/store/use-project";
|
||||
import { useWorkspace } from "@/hooks/store/use-workspace";
|
||||
import { useUserPermissions } from "@/hooks/store/user";
|
||||
import { getAnalyticsTabs } from "@/plane-web/components/analytics/tabs";
|
||||
import type { Route } from "./+types/page";
|
||||
|
||||
type Props = {
|
||||
params: {
|
||||
tabId: string;
|
||||
workspaceSlug: string;
|
||||
};
|
||||
};
|
||||
|
||||
const AnalyticsPage = observer((props: Props) => {
|
||||
// props
|
||||
const { params } = props;
|
||||
function AnalyticsPage({ params }: Route.ComponentProps) {
|
||||
const { tabId } = params;
|
||||
|
||||
// hooks
|
||||
@@ -68,7 +60,7 @@ const AnalyticsPage = observer((props: Props) => {
|
||||
})),
|
||||
[ANALYTICS_TABS, router, currentWorkspace?.slug]
|
||||
);
|
||||
const defaultTab = tabId || ANALYTICS_TABS[0].key;
|
||||
const defaultTab = tabId;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -111,6 +103,6 @@ const AnalyticsPage = observer((props: Props) => {
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default AnalyticsPage;
|
||||
export default observer(AnalyticsPage);
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
"use client";
|
||||
|
||||
// components
|
||||
import { Outlet } from "react-router";
|
||||
import { AppHeader } from "@/components/core/app-header";
|
||||
import { ContentWrapper } from "@/components/core/content-wrapper";
|
||||
import { ProjectIssueDetailsHeader } from "./header";
|
||||
|
||||
export default function ProjectIssueDetailsLayout({ children }: { children: React.ReactNode }) {
|
||||
export default function ProjectIssueDetailsLayout() {
|
||||
return (
|
||||
<>
|
||||
<AppHeader header={<ProjectIssueDetailsHeader />} />
|
||||
<ContentWrapper className="overflow-hidden">{children}</ContentWrapper>
|
||||
<ContentWrapper className="overflow-hidden">
|
||||
<Outlet />
|
||||
</ContentWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
"use client";
|
||||
|
||||
import React, { useEffect } from "react";
|
||||
import { useEffect } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { useTheme } from "next-themes";
|
||||
import useSWR from "swr";
|
||||
// plane imports
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { EIssueServiceType } from "@plane/types";
|
||||
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
|
||||
import { EmptyState } from "@/components/common/empty-state";
|
||||
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 { useIssueDetail } from "@/hooks/store/use-issue-detail";
|
||||
import { useProject } from "@/hooks/store/use-project";
|
||||
// assets
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
// plane web imports
|
||||
import { useWorkItemProperties } from "@/plane-web/hooks/use-issue-properties";
|
||||
import { ProjectAuthWrapper } from "@/plane-web/layouts/project-wrapper";
|
||||
import emptyIssueDark from "@/public/empty-state/search/issues-dark.webp";
|
||||
import emptyIssueLight from "@/public/empty-state/search/issues-light.webp";
|
||||
import type { Route } from "./+types/page";
|
||||
|
||||
const IssueDetailsPage = observer(() => {
|
||||
function IssueDetailsPage({ params }: Route.ComponentProps) {
|
||||
// router
|
||||
const router = useAppRouter();
|
||||
const { workspaceSlug, workItem } = useParams();
|
||||
const { workspaceSlug, workItem } = params;
|
||||
// hooks
|
||||
const { resolvedTheme } = useTheme();
|
||||
// store hooks
|
||||
@@ -39,15 +40,11 @@ const IssueDetailsPage = observer(() => {
|
||||
const { getProjectById } = useProject();
|
||||
const { toggleIssueDetailSidebar, issueDetailSidebarCollapsed } = useAppTheme();
|
||||
|
||||
const projectIdentifier = workItem?.toString().split("-")[0];
|
||||
const sequence_id = workItem?.toString().split("-")[1];
|
||||
const [projectIdentifier, sequence_id] = workItem.split("-");
|
||||
|
||||
// fetching issue details
|
||||
const { data, isLoading, error } = useSWR(
|
||||
workspaceSlug && workItem ? `ISSUE_DETAIL_${workspaceSlug}_${projectIdentifier}_${sequence_id}` : null,
|
||||
workspaceSlug && workItem
|
||||
? () => fetchIssueWithIdentifier(workspaceSlug.toString(), projectIdentifier, sequence_id)
|
||||
: null
|
||||
const { data, isLoading, error } = useSWR(`ISSUE_DETAIL_${workspaceSlug}_${projectIdentifier}_${sequence_id}`, () =>
|
||||
fetchIssueWithIdentifier(workspaceSlug.toString(), projectIdentifier, sequence_id)
|
||||
);
|
||||
const issueId = data?.id;
|
||||
const projectId = data?.project_id;
|
||||
@@ -113,14 +110,13 @@ const IssueDetailsPage = observer(() => {
|
||||
</div>
|
||||
</Loader>
|
||||
) : (
|
||||
workspaceSlug &&
|
||||
projectId &&
|
||||
issueId && (
|
||||
<ProjectAuthWrapper workspaceSlug={workspaceSlug?.toString()} projectId={projectId?.toString()}>
|
||||
<ProjectAuthWrapper workspaceSlug={workspaceSlug} projectId={projectId}>
|
||||
<IssueDetailRoot
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
projectId={projectId.toString()}
|
||||
issueId={issueId.toString()}
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
issueId={issueId}
|
||||
is_archived={!!issue?.archived_at}
|
||||
/>
|
||||
</ProjectAuthWrapper>
|
||||
@@ -128,6 +124,6 @@ const IssueDetailsPage = observer(() => {
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default IssueDetailsPage;
|
||||
export default observer(IssueDetailsPage);
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
"use client";
|
||||
|
||||
// components
|
||||
import { Outlet } from "react-router";
|
||||
import { AppHeader } from "@/components/core/app-header";
|
||||
import { ContentWrapper } from "@/components/core/content-wrapper";
|
||||
// local imports
|
||||
import { WorkspaceDraftHeader } from "./header";
|
||||
|
||||
export default function WorkspaceDraftLayout({ children }: { children: React.ReactNode }) {
|
||||
export default function WorkspaceDraftLayout() {
|
||||
return (
|
||||
<>
|
||||
<AppHeader header={<WorkspaceDraftHeader />} />
|
||||
<ContentWrapper>{children}</ContentWrapper>
|
||||
<ContentWrapper>
|
||||
<Outlet />
|
||||
</ContentWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
"use client";
|
||||
|
||||
import { useParams } from "next/navigation";
|
||||
// components
|
||||
import { PageHead } from "@/components/core/page-title";
|
||||
import { WorkspaceDraftIssuesRoot } from "@/components/issues/workspace-draft";
|
||||
import type { Route } from "./+types/page";
|
||||
|
||||
const WorkspaceDraftPage = () => {
|
||||
// router
|
||||
const { workspaceSlug: routeWorkspaceSlug } = useParams();
|
||||
function WorkspaceDraftPage({ params }: Route.ComponentProps) {
|
||||
const { workspaceSlug } = params;
|
||||
const pageTitle = "Workspace Draft";
|
||||
|
||||
// derived values
|
||||
const workspaceSlug = (routeWorkspaceSlug as string) || undefined;
|
||||
|
||||
if (!workspaceSlug) return null;
|
||||
return (
|
||||
<>
|
||||
<PageHead title={pageTitle} />
|
||||
@@ -22,6 +17,6 @@ const WorkspaceDraftPage = () => {
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default WorkspaceDraftPage;
|
||||
|
||||
@@ -1,33 +1,30 @@
|
||||
"use client";
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { Outlet } from "react-router";
|
||||
import { ProjectsAppPowerKProvider } from "@/components/power-k/projects-app-provider";
|
||||
import { AuthenticationWrapper } from "@/lib/wrappers/authentication-wrapper";
|
||||
// plane web components
|
||||
import { WorkspaceAuthWrapper } from "@/plane-web/layouts/workspace-wrapper";
|
||||
import { ProjectAppSidebar } from "./_sidebar";
|
||||
|
||||
const WorkspaceLayoutContent = observer(({ children }: { children: React.ReactNode }) => (
|
||||
<>
|
||||
<ProjectsAppPowerKProvider />
|
||||
<WorkspaceAuthWrapper>
|
||||
<div className="relative flex flex-col h-full w-full overflow-hidden rounded-lg border border-custom-border-200">
|
||||
<div id="full-screen-portal" className="inset-0 absolute w-full" />
|
||||
<div className="relative flex size-full overflow-hidden">
|
||||
<ProjectAppSidebar />
|
||||
<main className="relative flex h-full w-full flex-col overflow-hidden bg-custom-background-100">
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</WorkspaceAuthWrapper>
|
||||
</>
|
||||
));
|
||||
|
||||
export default function WorkspaceLayout({ children }: { children: React.ReactNode }) {
|
||||
function WorkspaceLayout() {
|
||||
return (
|
||||
<AuthenticationWrapper>
|
||||
<WorkspaceLayoutContent>{children}</WorkspaceLayoutContent>
|
||||
<ProjectsAppPowerKProvider />
|
||||
<WorkspaceAuthWrapper>
|
||||
<div className="relative flex flex-col h-full w-full overflow-hidden rounded-lg border border-custom-border-200">
|
||||
<div id="full-screen-portal" className="inset-0 absolute w-full" />
|
||||
<div className="relative flex size-full overflow-hidden">
|
||||
<ProjectAppSidebar />
|
||||
<main className="relative flex h-full w-full flex-col overflow-hidden bg-custom-background-100">
|
||||
<Outlet />
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</WorkspaceAuthWrapper>
|
||||
</AuthenticationWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
export default observer(WorkspaceLayout);
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
"use client";
|
||||
|
||||
import { Outlet } from "react-router";
|
||||
// components
|
||||
import { NotificationsSidebarRoot } from "@/components/workspace-notifications/sidebar";
|
||||
|
||||
export default function ProjectInboxIssuesLayout({ children }: { children: React.ReactNode }) {
|
||||
export default function ProjectInboxIssuesLayout() {
|
||||
return (
|
||||
<div className="relative w-full h-full overflow-hidden flex items-center">
|
||||
<NotificationsSidebarRoot />
|
||||
<div className="w-full h-full overflow-hidden overflow-y-auto">{children}</div>
|
||||
<div className="w-full h-full overflow-hidden overflow-y-auto">
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// plane imports
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// components
|
||||
@@ -9,9 +8,10 @@ import { PageHead } from "@/components/core/page-title";
|
||||
import { NotificationsRoot } from "@/components/workspace-notifications";
|
||||
// hooks
|
||||
import { useWorkspace } from "@/hooks/store/use-workspace";
|
||||
import type { Route } from "./+types/page";
|
||||
|
||||
const WorkspaceDashboardPage = observer(() => {
|
||||
const { workspaceSlug } = useParams();
|
||||
function WorkspaceDashboardPage({ params }: Route.ComponentProps) {
|
||||
const { workspaceSlug } = params;
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// hooks
|
||||
@@ -24,9 +24,9 @@ const WorkspaceDashboardPage = observer(() => {
|
||||
return (
|
||||
<>
|
||||
<PageHead title={pageTitle} />
|
||||
<NotificationsRoot workspaceSlug={workspaceSlug?.toString()} />
|
||||
<NotificationsRoot workspaceSlug={workspaceSlug} />
|
||||
</>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default WorkspaceDashboardPage;
|
||||
export default observer(WorkspaceDashboardPage);
|
||||
|
||||
@@ -12,7 +12,7 @@ import { useWorkspace } from "@/hooks/store/use-workspace";
|
||||
// local components
|
||||
import { WorkspaceDashboardHeader } from "./header";
|
||||
|
||||
const WorkspaceDashboardPage = observer(() => {
|
||||
function WorkspaceDashboardPage() {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { t } = useTranslation();
|
||||
// derived values
|
||||
@@ -27,6 +27,6 @@ const WorkspaceDashboardPage = observer(() => {
|
||||
</ContentWrapper>
|
||||
</>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default WorkspaceDashboardPage;
|
||||
export default observer(WorkspaceDashboardPage);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { useParams } from "next/navigation";
|
||||
// components
|
||||
import { PageHead } from "@/components/core/page-title";
|
||||
import { ProfileIssuesPage } from "@/components/profile/profile-issues";
|
||||
import type { Route } from "./+types/page";
|
||||
|
||||
const ProfilePageHeader = {
|
||||
assigned: "Profile - Assigned",
|
||||
@@ -12,10 +12,14 @@ const ProfilePageHeader = {
|
||||
subscribed: "Profile - Subscribed",
|
||||
};
|
||||
|
||||
const ProfileIssuesTypePage = () => {
|
||||
const { profileViewId } = useParams() as { profileViewId: "assigned" | "subscribed" | "created" | undefined };
|
||||
function isValidProfileViewId(viewId: string): viewId is keyof typeof ProfilePageHeader {
|
||||
return viewId in ProfilePageHeader;
|
||||
}
|
||||
|
||||
if (!profileViewId) return null;
|
||||
function ProfileIssuesTypePage({ params }: Route.ComponentProps) {
|
||||
const { profileViewId } = params;
|
||||
|
||||
if (!isValidProfileViewId(profileViewId)) return null;
|
||||
|
||||
const header = ProfilePageHeader[profileViewId];
|
||||
|
||||
@@ -25,6 +29,6 @@ const ProfileIssuesTypePage = () => {
|
||||
<ProfileIssuesPage type={profileViewId} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default ProfileIssuesTypePage;
|
||||
|
||||
@@ -15,7 +15,7 @@ import { useUserPermissions } from "@/hooks/store/user";
|
||||
|
||||
const PER_PAGE = 100;
|
||||
|
||||
const ProfileActivityPage = observer(() => {
|
||||
function ProfileActivityPage() {
|
||||
// states
|
||||
const [pageCount, setPageCount] = useState(1);
|
||||
const [totalPages, setTotalPages] = useState(0);
|
||||
@@ -69,6 +69,6 @@ const ProfileActivityPage = observer(() => {
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default ProfileActivityPage;
|
||||
export default observer(ProfileActivityPage);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
"use client";
|
||||
|
||||
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";
|
||||
// components
|
||||
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";
|
||||
// local components
|
||||
import { UserService } from "@/services/user.service";
|
||||
import type { Route } from "./+types/layout";
|
||||
import { UserProfileHeader } from "./header";
|
||||
import { ProfileIssuesMobileHeader } from "./mobile-header";
|
||||
import { ProfileNavbar } from "./navbar";
|
||||
|
||||
const userService = new UserService();
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const UseProfileLayout: React.FC<Props> = observer((props) => {
|
||||
const { children } = props;
|
||||
function UseProfileLayout({ params }: Route.ComponentProps) {
|
||||
// router
|
||||
const { workspaceSlug, userId } = useParams();
|
||||
const { workspaceSlug, userId } = params;
|
||||
const pathname = usePathname();
|
||||
// store hooks
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
@@ -43,11 +40,8 @@ const UseProfileLayout: React.FC<Props> = observer((props) => {
|
||||
const windowSize = useSize();
|
||||
const isSmallerScreen = windowSize[0] >= 768;
|
||||
|
||||
const { data: userProjectsData } = useSWR(
|
||||
workspaceSlug && userId ? USER_PROFILE_PROJECT_SEGREGATION(workspaceSlug.toString(), userId.toString()) : null,
|
||||
workspaceSlug && userId
|
||||
? () => userService.getUserProfileProjectsSegregation(workspaceSlug.toString(), userId.toString())
|
||||
: null
|
||||
const { data: userProjectsData } = useSWR(USER_PROFILE_PROJECT_SEGREGATION(workspaceSlug, userId), () =>
|
||||
userService.getUserProfileProjectsSegregation(workspaceSlug, userId)
|
||||
);
|
||||
// derived values
|
||||
const isAuthorizedPath =
|
||||
@@ -60,7 +54,7 @@ const UseProfileLayout: React.FC<Props> = observer((props) => {
|
||||
return (
|
||||
<>
|
||||
{/* Passing the type prop from the current route value as we need the header as top most component.
|
||||
TODO: We are depending on the route path to handle the mobile header type. If the path changes, this logic will break. */}
|
||||
TODO: We are depending on the route path to handle the mobile header type. If the path changes, this logic will break. */}
|
||||
<div className="h-full w-full flex flex-col md:flex-row overflow-hidden">
|
||||
<div className="h-full w-full flex flex-col overflow-hidden">
|
||||
<AppHeader
|
||||
@@ -78,7 +72,9 @@ const UseProfileLayout: React.FC<Props> = observer((props) => {
|
||||
<div className="flex w-full flex-col md:h-full md:overflow-hidden">
|
||||
<ProfileNavbar isAuthorized={!!isAuthorized} />
|
||||
{isAuthorized || !isAuthorizedPath ? (
|
||||
<div className={`w-full overflow-hidden h-full`}>{children}</div>
|
||||
<div className={`w-full overflow-hidden h-full`}>
|
||||
<Outlet />
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid h-full w-full place-items-center text-custom-text-200">
|
||||
{t("you_do_not_have_the_permission_to_access_this_page")}
|
||||
@@ -93,6 +89,6 @@ const UseProfileLayout: React.FC<Props> = observer((props) => {
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default UseProfileLayout;
|
||||
export default observer(UseProfileLayout);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { useParams } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
// plane imports
|
||||
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";
|
||||
// services
|
||||
import { UserService } from "@/services/user.service";
|
||||
import type { Route } from "./+types/page";
|
||||
const userService = new UserService();
|
||||
|
||||
export default function ProfileOverviewPage() {
|
||||
const { workspaceSlug, userId } = useParams();
|
||||
export default function ProfileOverviewPage({ params }: Route.ComponentProps) {
|
||||
const { workspaceSlug, userId } = params;
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { data: userProfile } = useSWR(
|
||||
workspaceSlug && userId ? USER_PROFILE_DATA(workspaceSlug.toString(), userId.toString()) : null,
|
||||
workspaceSlug && userId ? () => userService.getUserProfileData(workspaceSlug.toString(), userId.toString()) : null
|
||||
const { data: userProfile } = useSWR(USER_PROFILE_DATA(workspaceSlug, userId), () =>
|
||||
userService.getUserProfileData(workspaceSlug, userId)
|
||||
);
|
||||
|
||||
const stateDistribution: IUserStateDistribution[] = Object.keys(GROUP_CHOICES).map((key) => {
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import { Outlet } from "react-router";
|
||||
// components
|
||||
import { AppHeader } from "@/components/core/app-header";
|
||||
import { ContentWrapper } from "@/components/core/content-wrapper";
|
||||
import { ProjectArchivesHeader } from "../header";
|
||||
|
||||
export default function ProjectArchiveCyclesLayout({ children }: { children: React.ReactNode }) {
|
||||
export default function ProjectArchiveCyclesLayout() {
|
||||
return (
|
||||
<>
|
||||
<AppHeader header={<ProjectArchivesHeader activeTab="cycles" />} />
|
||||
<ContentWrapper>{children}</ContentWrapper>
|
||||
<ContentWrapper>
|
||||
<Outlet />
|
||||
</ContentWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
"use client";
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// components
|
||||
import { PageHead } from "@/components/core/page-title";
|
||||
import { ArchivedCycleLayoutRoot } from "@/components/cycles/archived-cycles";
|
||||
import { ArchivedCyclesHeader } from "@/components/cycles/archived-cycles/header";
|
||||
// hooks
|
||||
import { useProject } from "@/hooks/store/use-project";
|
||||
import type { Route } from "./+types/page";
|
||||
|
||||
const ProjectArchivedCyclesPage = observer(() => {
|
||||
function ProjectArchivedCyclesPage({ params }: Route.ComponentProps) {
|
||||
// router
|
||||
const { projectId } = useParams();
|
||||
const { projectId } = params;
|
||||
// store hooks
|
||||
const { getProjectById } = useProject();
|
||||
// derived values
|
||||
const project = projectId ? getProjectById(projectId.toString()) : undefined;
|
||||
const project = getProjectById(projectId);
|
||||
const pageTitle = project?.name && `${project?.name} - Archived cycles`;
|
||||
|
||||
return (
|
||||
@@ -27,6 +27,6 @@ const ProjectArchivedCyclesPage = observer(() => {
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default ProjectArchivedCyclesPage;
|
||||
export default observer(ProjectArchivedCyclesPage);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import { useRouter } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
// ui
|
||||
import { Banner } from "@plane/propel/banner";
|
||||
@@ -15,10 +15,11 @@ import { IssueDetailRoot } from "@/components/issues/issue-detail";
|
||||
// hooks
|
||||
import { useIssueDetail } from "@/hooks/store/use-issue-detail";
|
||||
import { useProject } from "@/hooks/store/use-project";
|
||||
import type { Route } from "./+types/page";
|
||||
|
||||
const ArchivedIssueDetailsPage = observer(() => {
|
||||
function ArchivedIssueDetailsPage({ params }: Route.ComponentProps) {
|
||||
// router
|
||||
const { workspaceSlug, projectId, archivedIssueId } = useParams();
|
||||
const { workspaceSlug, projectId, archivedIssueId } = params;
|
||||
const router = useRouter();
|
||||
// states
|
||||
// hooks
|
||||
@@ -29,17 +30,12 @@ const ArchivedIssueDetailsPage = observer(() => {
|
||||
|
||||
const { getProjectById } = useProject();
|
||||
|
||||
const { isLoading } = useSWR(
|
||||
workspaceSlug && projectId && archivedIssueId
|
||||
? `ARCHIVED_ISSUE_DETAIL_${workspaceSlug}_${projectId}_${archivedIssueId}`
|
||||
: null,
|
||||
workspaceSlug && projectId && archivedIssueId
|
||||
? () => fetchIssue(workspaceSlug.toString(), projectId.toString(), archivedIssueId.toString())
|
||||
: null
|
||||
const { isLoading } = useSWR(`ARCHIVED_ISSUE_DETAIL_${workspaceSlug}_${projectId}_${archivedIssueId}`, () =>
|
||||
fetchIssue(workspaceSlug, projectId, archivedIssueId)
|
||||
);
|
||||
|
||||
// derived values
|
||||
const issue = archivedIssueId ? getIssueById(archivedIssueId.toString()) : undefined;
|
||||
const issue = getIssueById(archivedIssueId);
|
||||
const project = issue ? getProjectById(issue?.project_id ?? "") : undefined;
|
||||
const pageTitle = project && issue ? `${project?.identifier}-${issue?.sequence_id} ${issue?.name}` : undefined;
|
||||
|
||||
@@ -84,20 +80,18 @@ const ArchivedIssueDetailsPage = observer(() => {
|
||||
/>
|
||||
<div className="flex h-full overflow-hidden">
|
||||
<div className="h-full w-full space-y-3 divide-y-2 divide-custom-border-200 overflow-y-auto">
|
||||
{workspaceSlug && projectId && archivedIssueId && (
|
||||
<IssueDetailRoot
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
projectId={projectId.toString()}
|
||||
issueId={archivedIssueId.toString()}
|
||||
is_archived
|
||||
/>
|
||||
)}
|
||||
<IssueDetailRoot
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
issueId={archivedIssueId}
|
||||
is_archived
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default ArchivedIssueDetailsPage;
|
||||
export default observer(ArchivedIssueDetailsPage);
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import { Outlet } from "react-router";
|
||||
// components
|
||||
import { AppHeader } from "@/components/core/app-header";
|
||||
import { ContentWrapper } from "@/components/core/content-wrapper";
|
||||
import { ProjectArchivedIssueDetailsHeader } from "./header";
|
||||
|
||||
export default function ProjectArchivedIssueDetailLayout({ children }: { children: React.ReactNode }) {
|
||||
export default function ProjectArchivedIssueDetailLayout() {
|
||||
return (
|
||||
<>
|
||||
<AppHeader header={<ProjectArchivedIssueDetailsHeader />} />
|
||||
<ContentWrapper>{children}</ContentWrapper>
|
||||
<ContentWrapper>
|
||||
<Outlet />
|
||||
</ContentWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import { Outlet } from "react-router";
|
||||
// components
|
||||
import { AppHeader } from "@/components/core/app-header";
|
||||
import { ContentWrapper } from "@/components/core/content-wrapper";
|
||||
import { ProjectArchivesHeader } from "../../header";
|
||||
|
||||
export default function ProjectArchiveIssuesLayout({ children }: { children: React.ReactNode }) {
|
||||
export default function ProjectArchiveIssuesLayout() {
|
||||
return (
|
||||
<>
|
||||
<AppHeader header={<ProjectArchivesHeader activeTab="issues" />} />
|
||||
<ContentWrapper>{children}</ContentWrapper>
|
||||
<ContentWrapper>
|
||||
<Outlet />
|
||||
</ContentWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
"use client";
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// components
|
||||
import { PageHead } from "@/components/core/page-title";
|
||||
import { ArchivedIssuesHeader } from "@/components/issues/archived-issues-header";
|
||||
import { ArchivedIssueLayoutRoot } from "@/components/issues/issue-layouts/roots/archived-issue-layout-root";
|
||||
// hooks
|
||||
import { useProject } from "@/hooks/store/use-project";
|
||||
import type { Route } from "./+types/page";
|
||||
|
||||
const ProjectArchivedIssuesPage = observer(() => {
|
||||
function ProjectArchivedIssuesPage({ params }: Route.ComponentProps) {
|
||||
// router
|
||||
const { projectId } = useParams();
|
||||
const { projectId } = params;
|
||||
// store hooks
|
||||
const { getProjectById } = useProject();
|
||||
// derived values
|
||||
const project = projectId ? getProjectById(projectId.toString()) : undefined;
|
||||
const project = getProjectById(projectId);
|
||||
const pageTitle = project?.name && `${project?.name} - Archived work items`;
|
||||
|
||||
return (
|
||||
@@ -27,6 +27,6 @@ const ProjectArchivedIssuesPage = observer(() => {
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default ProjectArchivedIssuesPage;
|
||||
export default observer(ProjectArchivedIssuesPage);
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import { Outlet } from "react-router";
|
||||
// components
|
||||
import { AppHeader } from "@/components/core/app-header";
|
||||
import { ContentWrapper } from "@/components/core/content-wrapper";
|
||||
import { ProjectArchivesHeader } from "../header";
|
||||
|
||||
export default function ProjectArchiveModulesLayout({ children }: { children: React.ReactNode }) {
|
||||
export default function ProjectArchiveModulesLayout() {
|
||||
return (
|
||||
<>
|
||||
<AppHeader header={<ProjectArchivesHeader activeTab="modules" />} />
|
||||
<ContentWrapper>{children}</ContentWrapper>
|
||||
<ContentWrapper>
|
||||
<Outlet />
|
||||
</ContentWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
"use client";
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// components
|
||||
import { PageHead } from "@/components/core/page-title";
|
||||
import { ArchivedModuleLayoutRoot, ArchivedModulesHeader } from "@/components/modules";
|
||||
// hooks
|
||||
import { useProject } from "@/hooks/store/use-project";
|
||||
import type { Route } from "./+types/page";
|
||||
|
||||
const ProjectArchivedModulesPage = observer(() => {
|
||||
function ProjectArchivedModulesPage({ params }: Route.ComponentProps) {
|
||||
// router
|
||||
const { projectId } = useParams();
|
||||
const { projectId } = params;
|
||||
// store hooks
|
||||
const { getProjectById } = useProject();
|
||||
// derived values
|
||||
const project = projectId ? getProjectById(projectId.toString()) : undefined;
|
||||
const project = getProjectById(projectId);
|
||||
const pageTitle = project?.name && `${project?.name} - Archived modules`;
|
||||
|
||||
return (
|
||||
@@ -26,6 +26,6 @@ const ProjectArchivedModulesPage = observer(() => {
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default ProjectArchivedModulesPage;
|
||||
export default observer(ProjectArchivedModulesPage);
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// plane imports
|
||||
import { cn } from "@plane/utils";
|
||||
// assets
|
||||
import emptyCycle from "@/app/assets/empty-state/cycle.svg?url";
|
||||
// components
|
||||
import { EmptyState } from "@/components/common/empty-state";
|
||||
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 { useAppRouter } from "@/hooks/use-app-router";
|
||||
import useLocalStorage from "@/hooks/use-local-storage";
|
||||
// assets
|
||||
import emptyCycle from "@/public/empty-state/cycle.svg";
|
||||
import type { Route } from "./+types/page";
|
||||
|
||||
const CycleDetailPage = observer(() => {
|
||||
function CycleDetailPage({ params }: Route.ComponentProps) {
|
||||
// router
|
||||
const router = useAppRouter();
|
||||
const { workspaceSlug, projectId, cycleId } = useParams();
|
||||
const { workspaceSlug, projectId, cycleId } = params;
|
||||
// store hooks
|
||||
const { getCycleById, loader } = useCycle();
|
||||
const { getProjectById } = useProject();
|
||||
@@ -30,14 +30,14 @@ const CycleDetailPage = observer(() => {
|
||||
const { setValue, storedValue } = useLocalStorage("cycle_sidebar_collapsed", false);
|
||||
|
||||
useCyclesDetails({
|
||||
workspaceSlug: workspaceSlug?.toString(),
|
||||
projectId: projectId.toString(),
|
||||
cycleId: cycleId.toString(),
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
cycleId,
|
||||
});
|
||||
// derived values
|
||||
const isSidebarCollapsed = storedValue ? (storedValue === true ? true : false) : false;
|
||||
const cycle = cycleId ? getCycleById(cycleId.toString()) : undefined;
|
||||
const project = projectId ? getProjectById(projectId.toString()) : undefined;
|
||||
const cycle = getCycleById(cycleId);
|
||||
const project = getProjectById(projectId);
|
||||
const pageTitle = project?.name && cycle?.name ? `${project?.name} - ${cycle?.name}` : undefined;
|
||||
|
||||
/**
|
||||
@@ -46,7 +46,6 @@ const CycleDetailPage = observer(() => {
|
||||
const toggleSidebar = () => setValue(!isSidebarCollapsed);
|
||||
|
||||
// const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout;
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHead title={pageTitle} />
|
||||
@@ -66,7 +65,7 @@ const CycleDetailPage = observer(() => {
|
||||
<div className="h-full w-full overflow-hidden">
|
||||
<CycleLayoutRoot />
|
||||
</div>
|
||||
{cycleId && !isSidebarCollapsed && (
|
||||
{!isSidebarCollapsed && (
|
||||
<div
|
||||
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]"
|
||||
@@ -78,9 +77,9 @@ const CycleDetailPage = observer(() => {
|
||||
>
|
||||
<CycleDetailsSidebar
|
||||
handleClose={toggleSidebar}
|
||||
cycleId={cycleId.toString()}
|
||||
projectId={projectId.toString()}
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
cycleId={cycleId}
|
||||
projectId={projectId}
|
||||
workspaceSlug={workspaceSlug}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -89,6 +88,6 @@ const CycleDetailPage = observer(() => {
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default CycleDetailPage;
|
||||
export default observer(CycleDetailPage);
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
"use client";
|
||||
|
||||
import { Outlet } from "react-router";
|
||||
// components
|
||||
import { AppHeader } from "@/components/core/app-header";
|
||||
import { ContentWrapper } from "@/components/core/content-wrapper";
|
||||
import { CycleIssuesHeader } from "./header";
|
||||
import { CycleIssuesMobileHeader } from "./mobile-header";
|
||||
|
||||
export default function ProjectCycleIssuesLayout({ children }: { children: React.ReactNode }) {
|
||||
export default function ProjectCycleIssuesLayout() {
|
||||
return (
|
||||
<>
|
||||
<AppHeader header={<CycleIssuesHeader />} mobileHeader={<CycleIssuesMobileHeader />} />
|
||||
<ContentWrapper>{children}</ContentWrapper>
|
||||
<ContentWrapper>
|
||||
<Outlet />
|
||||
</ContentWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
"use client";
|
||||
|
||||
import { Outlet } from "react-router";
|
||||
// components
|
||||
import { AppHeader } from "@/components/core/app-header";
|
||||
import { ContentWrapper } from "@/components/core/content-wrapper";
|
||||
import { CyclesListHeader } from "./header";
|
||||
import { CyclesListMobileHeader } from "./mobile-header";
|
||||
|
||||
export default function ProjectCyclesListLayout({ children }: { children: React.ReactNode }) {
|
||||
export default function ProjectCyclesListLayout() {
|
||||
return (
|
||||
<>
|
||||
<AppHeader header={<CyclesListHeader />} mobileHeader={<CyclesListMobileHeader />} />
|
||||
<ContentWrapper>{children}</ContentWrapper>
|
||||
<ContentWrapper>
|
||||
<Outlet />
|
||||
</ContentWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// plane imports
|
||||
import { useTheme } from "next-themes";
|
||||
import { EUserPermissionsLevel, CYCLE_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { EmptyStateDetailed } from "@plane/propel/empty-state";
|
||||
@@ -12,6 +12,10 @@ import { EUserProjectRoles } from "@plane/types";
|
||||
// components
|
||||
import { Header, EHeaderVariant } from "@plane/ui";
|
||||
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 { CycleAppliedFiltersList } from "@/components/cycles/applied-filters";
|
||||
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 { useUserPermissions } from "@/hooks/store/user";
|
||||
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
|
||||
const [createModal, setCreateModal] = useState(false);
|
||||
// store hooks
|
||||
@@ -34,35 +38,34 @@ const ProjectCyclesPage = observer(() => {
|
||||
const { getProjectById, currentProjectDetails } = useProject();
|
||||
// router
|
||||
const router = useAppRouter();
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
const { workspaceSlug, projectId } = params;
|
||||
// theme hook
|
||||
const { resolvedTheme } = useTheme();
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// cycle filters hook
|
||||
const { clearAllFilters, currentProjectFilters, updateFilters } = useCycleFilter();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
// derived values
|
||||
const resolvedEmptyState = resolvedTheme === "light" ? lightEmptyState : darkEmptyState;
|
||||
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 hasAdminLevelPermission = allowPermissions([EUserProjectRoles.ADMIN], EUserPermissionsLevel.PROJECT);
|
||||
const hasMemberLevelPermission = allowPermissions(
|
||||
[EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER],
|
||||
EUserPermissionsLevel.PROJECT
|
||||
);
|
||||
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/disabled-feature/cycles" });
|
||||
|
||||
const handleRemoveFilter = (key: keyof TCycleFilters, value: string | null) => {
|
||||
if (!projectId) return;
|
||||
let newValues = currentProjectFilters?.[key] ?? [];
|
||||
|
||||
if (!value) newValues = [];
|
||||
else newValues = newValues.filter((val) => val !== value);
|
||||
|
||||
updateFilters(projectId.toString(), { [key]: newValues });
|
||||
updateFilters(projectId, { [key]: newValues });
|
||||
};
|
||||
|
||||
if (!workspaceSlug || !projectId) return <></>;
|
||||
|
||||
// No access to cycle
|
||||
if (currentProjectDetails?.cycle_view === false)
|
||||
return (
|
||||
@@ -70,7 +73,7 @@ const ProjectCyclesPage = observer(() => {
|
||||
<DetailedEmptyState
|
||||
title={t("disabled_project.empty_state.cycle.title")}
|
||||
description={t("disabled_project.empty_state.cycle.description")}
|
||||
assetPath={resolvedPath}
|
||||
assetPath={resolvedEmptyState}
|
||||
primaryButton={{
|
||||
text: t("disabled_project.empty_state.cycle.primary_button.text"),
|
||||
onClick: () => {
|
||||
@@ -89,8 +92,8 @@ const ProjectCyclesPage = observer(() => {
|
||||
<PageHead title={pageTitle} />
|
||||
<div className="w-full h-full">
|
||||
<CycleCreateUpdateModal
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
projectId={projectId.toString()}
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
isOpen={createModal}
|
||||
handleClose={() => setCreateModal(false)}
|
||||
/>
|
||||
@@ -117,18 +120,18 @@ const ProjectCyclesPage = observer(() => {
|
||||
<Header variant={EHeaderVariant.TERNARY}>
|
||||
<CycleAppliedFiltersList
|
||||
appliedFilters={currentProjectFilters ?? {}}
|
||||
handleClearAllFilters={() => clearAllFilters(projectId.toString())}
|
||||
handleClearAllFilters={() => clearAllFilters(projectId)}
|
||||
handleRemoveFilter={handleRemoveFilter}
|
||||
/>
|
||||
</Header>
|
||||
)}
|
||||
|
||||
<CyclesView workspaceSlug={workspaceSlug.toString()} projectId={projectId.toString()} />
|
||||
<CyclesView workspaceSlug={workspaceSlug} projectId={projectId} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default ProjectCyclesPage;
|
||||
export default observer(ProjectCyclesPage);
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import { Outlet } from "react-router";
|
||||
// components
|
||||
import { AppHeader } from "@/components/core/app-header";
|
||||
import { ContentWrapper } from "@/components/core/content-wrapper";
|
||||
import { ProjectInboxHeader } from "@/plane-web/components/projects/settings/intake/header";
|
||||
|
||||
export default function ProjectInboxIssuesLayout({ children }: { children: React.ReactNode }) {
|
||||
export default function ProjectInboxIssuesLayout() {
|
||||
return (
|
||||
<>
|
||||
<AppHeader header={<ProjectInboxHeader />} />
|
||||
<ContentWrapper>{children}</ContentWrapper>
|
||||
<ContentWrapper>
|
||||
<Outlet />
|
||||
</ContentWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
"use client";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams, useSearchParams } from "next/navigation";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { useTheme } from "next-themes";
|
||||
// plane imports
|
||||
import { EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
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
|
||||
import { PageHead } from "@/components/core/page-title";
|
||||
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 { useUserPermissions } from "@/hooks/store/user";
|
||||
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
|
||||
const router = useAppRouter();
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
const { workspaceSlug, projectId } = params;
|
||||
const searchParams = useSearchParams();
|
||||
const navigationTab = searchParams.get("currentTab");
|
||||
const inboxIssueId = searchParams.get("inboxIssueId");
|
||||
// theme hook
|
||||
const { resolvedTheme } = useTheme();
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// hooks
|
||||
@@ -29,7 +35,7 @@ const ProjectInboxPage = observer(() => {
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
// derived values
|
||||
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
|
||||
if (currentProjectDetails?.inbox_view === false)
|
||||
@@ -65,22 +71,20 @@ const ProjectInboxPage = observer(() => {
|
||||
: EInboxIssueCurrentTab.CLOSED
|
||||
: undefined;
|
||||
|
||||
if (!workspaceSlug || !projectId) return <></>;
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col">
|
||||
<PageHead title={pageTitle} />
|
||||
<div className="w-full h-full overflow-hidden">
|
||||
<InboxIssueRoot
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
projectId={projectId.toString()}
|
||||
inboxIssueId={inboxIssueId?.toString() || undefined}
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
inboxIssueId={inboxIssueId || undefined}
|
||||
inboxAccessible={currentProjectDetails?.inbox_view || false}
|
||||
navigationTab={currentNavigationTab}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default ProjectInboxPage;
|
||||
export default observer(ProjectInboxPage);
|
||||
|
||||
@@ -1,64 +1,68 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { useTheme } from "next-themes";
|
||||
import useSWR from "swr";
|
||||
import { redirect } from "react-router";
|
||||
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
|
||||
import { EmptyState } from "@/components/common/empty-state";
|
||||
import { LogoSpinner } from "@/components/common/logo-spinner";
|
||||
// hooks
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
// assets
|
||||
import emptyIssueDark from "@/public/empty-state/search/issues-dark.webp";
|
||||
import emptyIssueLight from "@/public/empty-state/search/issues-light.webp";
|
||||
// services
|
||||
import { IssueService } from "@/services/issue/issue.service";
|
||||
// types
|
||||
import type { Route } from "./+types/page";
|
||||
|
||||
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 { t } = useTranslation();
|
||||
const { workspaceSlug, projectId, issueId } = useParams();
|
||||
const { resolvedTheme } = useTheme();
|
||||
|
||||
const { data, isLoading, error } = useSWR(
|
||||
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 (
|
||||
<div className="flex items-center justify-center size-full">
|
||||
{error ? (
|
||||
if (loaderData.error) {
|
||||
return (
|
||||
<div className="flex items-center justify-center size-full">
|
||||
<EmptyState
|
||||
image={resolvedTheme === "dark" ? emptyIssueDark : emptyIssueLight}
|
||||
title={t("issue.empty_state.issue_detail.title")}
|
||||
description={t("issue.empty_state.issue_detail.description")}
|
||||
primaryButton={{
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center size-full">
|
||||
<LogoSpinner />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default IssueDetailsPage;
|
||||
}
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
"use client";
|
||||
|
||||
// components
|
||||
import { Outlet } from "react-router";
|
||||
import { AppHeader } from "@/components/core/app-header";
|
||||
import { ContentWrapper } from "@/components/core/content-wrapper";
|
||||
import { ProjectIssuesHeader } from "./header";
|
||||
import { ProjectIssuesMobileHeader } from "./mobile-header";
|
||||
|
||||
export default function ProjectIssuesLayout({ children }: { children: React.ReactNode }) {
|
||||
export default function ProjectIssuesLayout() {
|
||||
return (
|
||||
<>
|
||||
<AppHeader header={<ProjectIssuesHeader />} mobileHeader={<ProjectIssuesMobileHeader />} />
|
||||
<ContentWrapper>{children}</ContentWrapper>
|
||||
<ContentWrapper>
|
||||
<Outlet />
|
||||
</ContentWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import Head from "next/head";
|
||||
import { useParams } from "next/navigation";
|
||||
// i18n
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// components
|
||||
@@ -10,35 +8,27 @@ import { PageHead } from "@/components/core/page-title";
|
||||
import { ProjectLayoutRoot } from "@/components/issues/issue-layouts/roots/project-layout-root";
|
||||
// hooks
|
||||
import { useProject } from "@/hooks/store/use-project";
|
||||
import type { Route } from "./+types/page";
|
||||
|
||||
const ProjectIssuesPage = observer(() => {
|
||||
const { projectId } = useParams();
|
||||
function ProjectIssuesPage({ params }: Route.ComponentProps) {
|
||||
const { projectId } = params;
|
||||
// i18n
|
||||
const { t } = useTranslation();
|
||||
// store
|
||||
const { getProjectById } = useProject();
|
||||
|
||||
if (!projectId) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHead title={pageTitle} />
|
||||
<Head>
|
||||
<title>
|
||||
{project?.name} - {t("issue.label", { count: 2 })}
|
||||
</title>
|
||||
</Head>
|
||||
<div className="h-full w-full">
|
||||
<ProjectLayoutRoot />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default ProjectIssuesPage;
|
||||
export default observer(ProjectIssuesPage);
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
"use client";
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
// components
|
||||
// plane imports
|
||||
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 { PageHead } from "@/components/core/page-title";
|
||||
import { ModuleLayoutRoot } from "@/components/issues/issue-layouts/roots/module-layout-root";
|
||||
import { ModuleAnalyticsSidebar } from "@/components/modules";
|
||||
// helpers
|
||||
// hooks
|
||||
import { useModule } from "@/hooks/store/use-module";
|
||||
import { useProject } from "@/hooks/store/use-project";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
import useLocalStorage from "@/hooks/use-local-storage";
|
||||
// assets
|
||||
import emptyModule from "@/public/empty-state/module.svg";
|
||||
import type { Route } from "./+types/page";
|
||||
|
||||
const ModuleIssuesPage = observer(() => {
|
||||
function ModuleIssuesPage({ params }: Route.ComponentProps) {
|
||||
// router
|
||||
const router = useAppRouter();
|
||||
const { workspaceSlug, projectId, moduleId } = useParams();
|
||||
const { workspaceSlug, projectId, moduleId } = params;
|
||||
// store hooks
|
||||
const { fetchModuleDetails, getModuleById } = useModule();
|
||||
const { getProjectById } = useProject();
|
||||
@@ -30,25 +30,19 @@ const ModuleIssuesPage = observer(() => {
|
||||
const { setValue, storedValue } = useLocalStorage("module_sidebar_collapsed", "false");
|
||||
const isSidebarCollapsed = storedValue ? (storedValue === "true" ? true : false) : false;
|
||||
// fetching module details
|
||||
const { error } = useSWR(
|
||||
workspaceSlug && projectId && moduleId ? `CURRENT_MODULE_DETAILS_${moduleId.toString()}` : null,
|
||||
workspaceSlug && projectId && moduleId
|
||||
? () => fetchModuleDetails(workspaceSlug.toString(), projectId.toString(), moduleId.toString())
|
||||
: null
|
||||
const { error } = useSWR(`CURRENT_MODULE_DETAILS_${moduleId}`, () =>
|
||||
fetchModuleDetails(workspaceSlug, projectId, moduleId)
|
||||
);
|
||||
// derived values
|
||||
const projectModule = moduleId ? getModuleById(moduleId.toString()) : undefined;
|
||||
const project = projectId ? getProjectById(projectId.toString()) : undefined;
|
||||
const projectModule = getModuleById(moduleId);
|
||||
const project = getProjectById(projectId);
|
||||
const pageTitle = project?.name && projectModule?.name ? `${project?.name} - ${projectModule?.name}` : undefined;
|
||||
|
||||
const toggleSidebar = () => {
|
||||
setValue(`${!isSidebarCollapsed}`);
|
||||
};
|
||||
|
||||
if (!workspaceSlug || !projectId || !moduleId) return <></>;
|
||||
|
||||
// const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout;
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHead title={pageTitle} />
|
||||
@@ -67,7 +61,7 @@ const ModuleIssuesPage = observer(() => {
|
||||
<div className="h-full w-full overflow-hidden">
|
||||
<ModuleLayoutRoot />
|
||||
</div>
|
||||
{moduleId && !isSidebarCollapsed && (
|
||||
{!isSidebarCollapsed && (
|
||||
<div
|
||||
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]"
|
||||
@@ -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)",
|
||||
}}
|
||||
>
|
||||
<ModuleAnalyticsSidebar moduleId={moduleId.toString()} handleClose={toggleSidebar} />
|
||||
<ModuleAnalyticsSidebar moduleId={moduleId} handleClose={toggleSidebar} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default ModuleIssuesPage;
|
||||
export default observer(ModuleIssuesPage);
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
"use client";
|
||||
|
||||
import { Outlet } from "react-router";
|
||||
// components
|
||||
import { AppHeader } from "@/components/core/app-header";
|
||||
import { ContentWrapper } from "@/components/core/content-wrapper";
|
||||
import { ModuleIssuesHeader } from "./header";
|
||||
import { ModuleIssuesMobileHeader } from "./mobile-header";
|
||||
|
||||
export default function ProjectModuleIssuesLayout({ children }: { children: React.ReactNode }) {
|
||||
export default function ProjectModuleIssuesLayout() {
|
||||
return (
|
||||
<>
|
||||
<AppHeader header={<ModuleIssuesHeader />} mobileHeader={<ModuleIssuesMobileHeader />} />
|
||||
<ContentWrapper>{children}</ContentWrapper>
|
||||
<ContentWrapper>
|
||||
<Outlet />
|
||||
</ContentWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
"use client";
|
||||
|
||||
import { Outlet } from "react-router";
|
||||
// components
|
||||
import { AppHeader } from "@/components/core/app-header";
|
||||
import { ContentWrapper } from "@/components/core/content-wrapper";
|
||||
import { ModulesListHeader } from "./header";
|
||||
import { ModulesListMobileHeader } from "./mobile-header";
|
||||
|
||||
export default function ProjectModulesListLayout({ children }: { children: React.ReactNode }) {
|
||||
export default function ProjectModulesListLayout() {
|
||||
return (
|
||||
<>
|
||||
<AppHeader header={<ModulesListHeader />} mobileHeader={<ModulesListMobileHeader />} />
|
||||
<ContentWrapper>{children}</ContentWrapper>
|
||||
<ContentWrapper>
|
||||
<Outlet />
|
||||
</ContentWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,57 +2,63 @@
|
||||
|
||||
import { useCallback } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// types
|
||||
import { useTheme } from "next-themes";
|
||||
// plane imports
|
||||
import { EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import type { TModuleFilters } from "@plane/types";
|
||||
import { EUserProjectRoles } from "@plane/types";
|
||||
// components
|
||||
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 { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root";
|
||||
import { ModuleAppliedFiltersList, ModulesListView } from "@/components/modules";
|
||||
// helpers
|
||||
// hooks
|
||||
import { useModuleFilter } from "@/hooks/store/use-module-filter";
|
||||
import { useProject } from "@/hooks/store/use-project";
|
||||
import { useUserPermissions } from "@/hooks/store/user";
|
||||
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
|
||||
const router = useAppRouter();
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
const { workspaceSlug, projectId } = params;
|
||||
// theme hook
|
||||
const { resolvedTheme } = useTheme();
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// store
|
||||
const { getProjectById, currentProjectDetails } = useProject();
|
||||
const { currentProjectFilters, currentProjectDisplayFilters, clearAllFilters, updateFilters, updateDisplayFilters } =
|
||||
useModuleFilter();
|
||||
const {
|
||||
currentProjectFilters = {},
|
||||
currentProjectDisplayFilters,
|
||||
clearAllFilters,
|
||||
updateFilters,
|
||||
updateDisplayFilters,
|
||||
} = useModuleFilter();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
// derived values
|
||||
const project = projectId ? getProjectById(projectId.toString()) : undefined;
|
||||
const project = getProjectById(projectId);
|
||||
const pageTitle = project?.name ? `${project?.name} - Modules` : undefined;
|
||||
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(
|
||||
(key: keyof TModuleFilters, value: string | null) => {
|
||||
if (!projectId) return;
|
||||
let newValues = currentProjectFilters?.[key] ?? [];
|
||||
let newValues = currentProjectFilters[key] ?? [];
|
||||
|
||||
if (!value) newValues = [];
|
||||
else newValues = newValues.filter((val) => val !== value);
|
||||
|
||||
updateFilters(projectId.toString(), { [key]: newValues });
|
||||
updateFilters(projectId, { [key]: newValues });
|
||||
},
|
||||
[currentProjectFilters, projectId, updateFilters]
|
||||
);
|
||||
|
||||
if (!workspaceSlug || !projectId) return <></>;
|
||||
|
||||
// No access to
|
||||
if (currentProjectDetails?.module_view === false)
|
||||
return (
|
||||
@@ -76,16 +82,13 @@ const ProjectModulesPage = observer(() => {
|
||||
<>
|
||||
<PageHead title={pageTitle} />
|
||||
<div className="h-full w-full flex flex-col">
|
||||
{(calculateTotalFilters(currentProjectFilters ?? {}) !== 0 || currentProjectDisplayFilters?.favorites) && (
|
||||
{(calculateTotalFilters(currentProjectFilters) !== 0 || currentProjectDisplayFilters?.favorites) && (
|
||||
<ModuleAppliedFiltersList
|
||||
appliedFilters={currentProjectFilters ?? {}}
|
||||
appliedFilters={currentProjectFilters}
|
||||
isFavoriteFilterApplied={currentProjectDisplayFilters?.favorites ?? false}
|
||||
handleClearAllFilters={() => clearAllFilters(`${projectId}`)}
|
||||
handleClearAllFilters={() => clearAllFilters(projectId)}
|
||||
handleRemoveFilter={handleRemoveFilter}
|
||||
handleDisplayFiltersUpdate={(val) => {
|
||||
if (!projectId) return;
|
||||
updateDisplayFilters(projectId.toString(), val);
|
||||
}}
|
||||
handleDisplayFiltersUpdate={(val) => updateDisplayFilters(projectId, val)}
|
||||
alwaysAllowEditing
|
||||
/>
|
||||
)}
|
||||
@@ -93,6 +96,6 @@ const ProjectModulesPage = observer(() => {
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default ProjectModulesPage;
|
||||
export default observer(ProjectModulesPage);
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { useCallback, useEffect, useMemo } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
import { useParams } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
// plane types
|
||||
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";
|
||||
// services
|
||||
import { ProjectPageService, ProjectPageVersionService } from "@/services/page";
|
||||
import type { Route } from "./+types/page";
|
||||
const workspaceService = new WorkspaceService();
|
||||
const projectPageService = new ProjectPageService();
|
||||
const projectPageVersionService = new ProjectPageVersionService();
|
||||
|
||||
const storeType = EPageStoreType.PROJECT;
|
||||
|
||||
const PageDetailsPage = observer(() => {
|
||||
function PageDetailsPage({ params }: Route.ComponentProps) {
|
||||
// router
|
||||
const router = useAppRouter();
|
||||
const { workspaceSlug, projectId, pageId } = useParams();
|
||||
const { workspaceSlug, projectId, pageId } = params;
|
||||
// store hooks
|
||||
const { createPage, fetchPageDetails } = usePageStore(storeType);
|
||||
const page = usePage({
|
||||
pageId: pageId?.toString() ?? "",
|
||||
pageId,
|
||||
storeType,
|
||||
});
|
||||
const { getWorkspaceBySlug } = useWorkspace();
|
||||
const { uploadEditorAsset } = useEditorAsset();
|
||||
// derived values
|
||||
const workspaceId = workspaceSlug ? (getWorkspaceBySlug(workspaceSlug.toString())?.id ?? "") : "";
|
||||
const workspaceId = workspaceSlug ? (getWorkspaceBySlug(workspaceSlug)?.id ?? "") : "";
|
||||
const { canCurrentUserAccessPage, id, name, updateDescription } = page ?? {};
|
||||
// entity search handler
|
||||
const fetchEntityCallback = useCallback(
|
||||
async (payload: TSearchEntityRequestPayload) =>
|
||||
await workspaceService.searchEntity(workspaceSlug?.toString() ?? "", {
|
||||
await workspaceService.searchEntity(workspaceSlug, {
|
||||
...payload,
|
||||
project_id: projectId?.toString() ?? "",
|
||||
project_id: projectId,
|
||||
}),
|
||||
[projectId, workspaceSlug]
|
||||
);
|
||||
@@ -63,10 +63,8 @@ const PageDetailsPage = observer(() => {
|
||||
const { getEditorFileHandlers } = useEditorConfig();
|
||||
// fetch page details
|
||||
const { error: pageDetailsError } = useSWR(
|
||||
workspaceSlug && projectId && pageId ? `PAGE_DETAILS_${pageId}` : null,
|
||||
workspaceSlug && projectId && pageId
|
||||
? () => fetchPageDetails(workspaceSlug?.toString(), projectId?.toString(), pageId.toString())
|
||||
: null,
|
||||
`PAGE_DETAILS_${pageId}`,
|
||||
() => fetchPageDetails(workspaceSlug, projectId, pageId),
|
||||
{
|
||||
revalidateIfStale: true,
|
||||
revalidateOnFocus: true,
|
||||
@@ -77,33 +75,17 @@ const PageDetailsPage = observer(() => {
|
||||
const pageRootHandlers: TPageRootHandlers = useMemo(
|
||||
() => ({
|
||||
create: createPage,
|
||||
fetchAllVersions: async (pageId) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
return await projectPageVersionService.fetchAllVersions(workspaceSlug.toString(), projectId.toString(), pageId);
|
||||
},
|
||||
fetchAllVersions: async (pageId) =>
|
||||
await projectPageVersionService.fetchAllVersions(workspaceSlug, projectId, pageId),
|
||||
fetchDescriptionBinary: async () => {
|
||||
if (!workspaceSlug || !projectId || !id) return;
|
||||
return await projectPageService.fetchDescriptionBinary(workspaceSlug.toString(), projectId.toString(), id);
|
||||
if (!id) return;
|
||||
return await projectPageService.fetchDescriptionBinary(workspaceSlug, projectId, id);
|
||||
},
|
||||
fetchEntity: fetchEntityCallback,
|
||||
fetchVersionDetails: async (pageId, versionId) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
return await projectPageVersionService.fetchVersionById(
|
||||
workspaceSlug.toString(),
|
||||
projectId.toString(),
|
||||
pageId,
|
||||
versionId
|
||||
);
|
||||
},
|
||||
restoreVersion: async (pageId, versionId) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
await projectPageVersionService.restoreVersion(
|
||||
workspaceSlug.toString(),
|
||||
projectId.toString(),
|
||||
pageId,
|
||||
versionId
|
||||
);
|
||||
},
|
||||
fetchVersionDetails: async (pageId, versionId) =>
|
||||
await projectPageVersionService.fetchVersionById(workspaceSlug, projectId, pageId, versionId),
|
||||
restoreVersion: async (pageId, versionId) =>
|
||||
await projectPageVersionService.restoreVersion(workspaceSlug, projectId, pageId, versionId),
|
||||
getRedirectionLink: (pageId) => {
|
||||
if (pageId) {
|
||||
return `/${workspaceSlug}/projects/${projectId}/pages/${pageId}`;
|
||||
@@ -119,7 +101,7 @@ const PageDetailsPage = observer(() => {
|
||||
const pageRootConfig: TPageRootConfig = useMemo(
|
||||
() => ({
|
||||
fileHandler: getEditorFileHandlers({
|
||||
projectId: projectId?.toString() ?? "",
|
||||
projectId,
|
||||
uploadFile: async (blockId, file) => {
|
||||
const { asset_id } = await uploadEditorAsset({
|
||||
blockId,
|
||||
@@ -128,13 +110,13 @@ const PageDetailsPage = observer(() => {
|
||||
entity_type: EFileAssetType.PAGE_DESCRIPTION,
|
||||
},
|
||||
file,
|
||||
projectId: projectId?.toString() ?? "",
|
||||
workspaceSlug: workspaceSlug?.toString() ?? "",
|
||||
projectId,
|
||||
workspaceSlug,
|
||||
});
|
||||
return asset_id;
|
||||
},
|
||||
workspaceId,
|
||||
workspaceSlug: workspaceSlug?.toString() ?? "",
|
||||
workspaceSlug,
|
||||
}),
|
||||
}),
|
||||
[getEditorFileHandlers, id, uploadEditorAsset, projectId, workspaceId, workspaceSlug]
|
||||
@@ -143,8 +125,8 @@ const PageDetailsPage = observer(() => {
|
||||
const webhookConnectionParams: TWebhookConnectionQueryParams = useMemo(
|
||||
() => ({
|
||||
documentType: "project_page",
|
||||
projectId: projectId?.toString() ?? "",
|
||||
workspaceSlug: workspaceSlug?.toString() ?? "",
|
||||
projectId,
|
||||
workspaceSlug,
|
||||
}),
|
||||
[projectId, workspaceSlug]
|
||||
);
|
||||
@@ -178,7 +160,7 @@ const PageDetailsPage = observer(() => {
|
||||
</div>
|
||||
);
|
||||
|
||||
if (!page || !workspaceSlug || !projectId) return null;
|
||||
if (!page) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -191,14 +173,14 @@ const PageDetailsPage = observer(() => {
|
||||
storeType={storeType}
|
||||
page={page}
|
||||
webhookConnectionParams={webhookConnectionParams}
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
projectId={projectId?.toString()}
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
/>
|
||||
<IssuePeekOverview />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default PageDetailsPage;
|
||||
export default observer(PageDetailsPage);
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
"use client";
|
||||
|
||||
// component
|
||||
import { useParams } from "next/navigation";
|
||||
import { Outlet } from "react-router";
|
||||
import useSWR from "swr";
|
||||
import { AppHeader } from "@/components/core/app-header";
|
||||
import { ContentWrapper } from "@/components/core/content-wrapper";
|
||||
// plane web hooks
|
||||
import { EPageStoreType, usePageStore } from "@/plane-web/hooks/store";
|
||||
// local components
|
||||
import type { Route } from "./+types/layout";
|
||||
import { PageDetailsHeader } from "./header";
|
||||
|
||||
export default function ProjectPageDetailsLayout({ children }: { children: React.ReactNode }) {
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
export default function ProjectPageDetailsLayout({ params }: Route.ComponentProps) {
|
||||
const { workspaceSlug, projectId } = params;
|
||||
const { fetchPagesList } = usePageStore(EPageStoreType.PROJECT);
|
||||
// fetching pages list
|
||||
useSWR(
|
||||
workspaceSlug && projectId ? `PROJECT_PAGES_${projectId}` : null,
|
||||
workspaceSlug && projectId ? () => fetchPagesList(workspaceSlug.toString(), projectId.toString()) : null
|
||||
);
|
||||
useSWR(`PROJECT_PAGES_${projectId}`, () => fetchPagesList(workspaceSlug, projectId));
|
||||
return (
|
||||
<>
|
||||
<AppHeader header={<PageDetailsHeader />} />
|
||||
<ContentWrapper>{children}</ContentWrapper>
|
||||
<ContentWrapper>
|
||||
<Outlet />
|
||||
</ContentWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
"use client";
|
||||
|
||||
import type { ReactNode } from "react";
|
||||
// components
|
||||
import { Outlet } from "react-router";
|
||||
import { AppHeader } from "@/components/core/app-header";
|
||||
import { ContentWrapper } from "@/components/core/content-wrapper";
|
||||
// local components
|
||||
import { PagesListHeader } from "./header";
|
||||
|
||||
export default function ProjectPagesListLayout({ children }: { children: ReactNode }) {
|
||||
export default function ProjectPagesListLayout() {
|
||||
return (
|
||||
<>
|
||||
<AppHeader header={<PagesListHeader />} />
|
||||
<ContentWrapper>{children}</ContentWrapper>
|
||||
<ContentWrapper>
|
||||
<Outlet />
|
||||
</ContentWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
"use client";
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams, useSearchParams } from "next/navigation";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { useTheme } from "next-themes";
|
||||
// plane imports
|
||||
import { EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import type { TPageNavigationTabs } 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
|
||||
import { PageHead } from "@/components/core/page-title";
|
||||
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 { useUserPermissions } from "@/hooks/store/user";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
// plane web hooks
|
||||
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
|
||||
const router = useAppRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const type = searchParams.get("type");
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
const { workspaceSlug, projectId } = params;
|
||||
// theme hook
|
||||
const { resolvedTheme } = useTheme();
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
const { getProjectById, currentProjectDetails } = useProject();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
// derived values
|
||||
const project = projectId ? getProjectById(projectId.toString()) : undefined;
|
||||
const project = getProjectById(projectId);
|
||||
const pageTitle = project?.name ? `${project?.name} - Pages` : undefined;
|
||||
const canPerformEmptyStateActions = allowPermissions([EUserProjectRoles.ADMIN], EUserPermissionsLevel.PROJECT);
|
||||
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/disabled-feature/pages" });
|
||||
|
||||
const currentPageType = (): TPageNavigationTabs => {
|
||||
const pageType = type?.toString();
|
||||
if (pageType === "private") return "private";
|
||||
if (pageType === "archived") return "archived";
|
||||
return "public";
|
||||
};
|
||||
|
||||
if (!workspaceSlug || !projectId) return <></>;
|
||||
const resolvedPath = resolvedTheme === "light" ? lightPagesAsset : darkPagesAsset;
|
||||
const pageType = getPageType(type);
|
||||
|
||||
// No access to cycle
|
||||
if (currentProjectDetails?.page_view === false)
|
||||
@@ -68,15 +72,15 @@ const ProjectPagesPage = observer(() => {
|
||||
<>
|
||||
<PageHead title={pageTitle} />
|
||||
<PagesListView
|
||||
pageType={currentPageType()}
|
||||
projectId={projectId.toString()}
|
||||
pageType={pageType}
|
||||
projectId={projectId}
|
||||
storeType={EPageStoreType.PROJECT}
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
workspaceSlug={workspaceSlug}
|
||||
>
|
||||
<PagesListRoot pageType={currentPageType()} storeType={EPageStoreType.PROJECT} />
|
||||
<PagesListRoot pageType={pageType} storeType={EPageStoreType.PROJECT} />
|
||||
</PagesListView>
|
||||
</>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default ProjectPagesPage;
|
||||
export default observer(ProjectPagesPage);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
// assets
|
||||
import emptyView from "@/app/assets/empty-state/view.svg?url";
|
||||
// components
|
||||
import { EmptyState } from "@/components/common/empty-state";
|
||||
import { PageHead } from "@/components/core/page-title";
|
||||
@@ -10,28 +11,22 @@ import { ProjectViewLayoutRoot } from "@/components/issues/issue-layouts/roots/p
|
||||
// hooks
|
||||
import { useProject } from "@/hooks/store/use-project";
|
||||
import { useProjectView } from "@/hooks/store/use-project-view";
|
||||
// assets
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
import emptyView from "@/public/empty-state/view.svg";
|
||||
import type { Route } from "./+types/page";
|
||||
|
||||
const ProjectViewIssuesPage = observer(() => {
|
||||
function ProjectViewIssuesPage({ params }: Route.ComponentProps) {
|
||||
// router
|
||||
const router = useAppRouter();
|
||||
const { workspaceSlug, projectId, viewId } = useParams();
|
||||
const { workspaceSlug, projectId, viewId } = params;
|
||||
// store hooks
|
||||
const { fetchViewDetails, getViewById } = useProjectView();
|
||||
const { getProjectById } = useProject();
|
||||
// derived values
|
||||
const projectView = viewId ? getViewById(viewId.toString()) : undefined;
|
||||
const project = projectId ? getProjectById(projectId.toString()) : undefined;
|
||||
const projectView = getViewById(viewId);
|
||||
const project = getProjectById(projectId);
|
||||
const pageTitle = project?.name && projectView?.name ? `${project?.name} - ${projectView?.name}` : undefined;
|
||||
|
||||
const { error } = useSWR(
|
||||
workspaceSlug && projectId && viewId ? `VIEW_DETAILS_${viewId.toString()}` : null,
|
||||
workspaceSlug && projectId && viewId
|
||||
? () => fetchViewDetails(workspaceSlug.toString(), projectId.toString(), viewId.toString())
|
||||
: null
|
||||
);
|
||||
const { error } = useSWR(`VIEW_DETAILS_${viewId}`, () => fetchViewDetails(workspaceSlug, projectId, viewId));
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
@@ -53,6 +48,6 @@ const ProjectViewIssuesPage = observer(() => {
|
||||
<ProjectViewLayoutRoot />
|
||||
</>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default ProjectViewIssuesPage;
|
||||
export default observer(ProjectViewIssuesPage);
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import { Outlet } from "react-router";
|
||||
import { AppHeader } from "@/components/core/app-header";
|
||||
import { ContentWrapper } from "@/components/core/content-wrapper";
|
||||
// local components
|
||||
import { ProjectViewIssuesHeader } from "./[viewId]/header";
|
||||
|
||||
export default function ProjectViewIssuesLayout({ children }: { children: React.ReactNode }) {
|
||||
export default function ProjectViewIssuesLayout() {
|
||||
return (
|
||||
<>
|
||||
<AppHeader header={<ProjectViewIssuesHeader />} />
|
||||
<ContentWrapper>{children}</ContentWrapper>
|
||||
<ContentWrapper>
|
||||
<Outlet />
|
||||
</ContentWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
"use client";
|
||||
|
||||
import { Outlet } from "react-router";
|
||||
import { AppHeader } from "@/components/core/app-header";
|
||||
import { ContentWrapper } from "@/components/core/content-wrapper";
|
||||
// local components
|
||||
import { ProjectViewsHeader } from "./header";
|
||||
import { ViewMobileHeader } from "./mobile-header";
|
||||
|
||||
export default function ProjectViewsListLayout({ children }: { children: React.ReactNode }) {
|
||||
export default function ProjectViewsListLayout() {
|
||||
return (
|
||||
<>
|
||||
<AppHeader header={<ProjectViewsHeader />} mobileHeader={<ViewMobileHeader />} />
|
||||
<ContentWrapper>{children}</ContentWrapper>
|
||||
<ContentWrapper>
|
||||
<Outlet />
|
||||
</ContentWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,31 +2,35 @@
|
||||
|
||||
import { useCallback } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// components
|
||||
import { useTheme } from "next-themes";
|
||||
// plane imports
|
||||
import { EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import type { EViewAccess, TViewFilterProps } from "@plane/types";
|
||||
import { EUserProjectRoles } from "@plane/types";
|
||||
import { Header, EHeaderVariant } from "@plane/ui";
|
||||
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 { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root";
|
||||
import { ViewAppliedFiltersList } from "@/components/views/applied-filters";
|
||||
import { ProjectViewsList } from "@/components/views/views-list";
|
||||
// constants
|
||||
// helpers
|
||||
// hooks
|
||||
import { useProject } from "@/hooks/store/use-project";
|
||||
import { useProjectView } from "@/hooks/store/use-project-view";
|
||||
import { useUserPermissions } from "@/hooks/store/user";
|
||||
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
|
||||
const router = useAppRouter();
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
const { workspaceSlug, projectId } = params;
|
||||
// theme hook
|
||||
const { resolvedTheme } = useTheme();
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// store
|
||||
@@ -34,10 +38,10 @@ const ProjectViewsPage = observer(() => {
|
||||
const { filters, updateFilters, clearAllFilters } = useProjectView();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
// derived values
|
||||
const project = projectId ? getProjectById(projectId.toString()) : undefined;
|
||||
const project = getProjectById(projectId);
|
||||
const pageTitle = project?.name ? `${project?.name} - Views` : undefined;
|
||||
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(
|
||||
(key: keyof TViewFilterProps, value: string | EViewAccess | null) => {
|
||||
@@ -58,8 +62,6 @@ const ProjectViewsPage = observer(() => {
|
||||
|
||||
const isFiltersApplied = calculateTotalFilters(filters?.filters ?? {}) !== 0;
|
||||
|
||||
if (!workspaceSlug || !projectId) return <></>;
|
||||
|
||||
// No access to
|
||||
if (currentProjectDetails?.issue_views_view === false)
|
||||
return (
|
||||
@@ -95,6 +97,6 @@ const ProjectViewsPage = observer(() => {
|
||||
<ProjectViewsList />
|
||||
</>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default ProjectViewsPage;
|
||||
export default observer(ProjectViewsPage);
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
"use client";
|
||||
|
||||
import type { ReactNode } from "react";
|
||||
// components
|
||||
import { Outlet } from "react-router";
|
||||
import { AppHeader } from "@/components/core/app-header";
|
||||
import { ContentWrapper } from "@/components/core/content-wrapper";
|
||||
// local components
|
||||
import { ProjectsListHeader } from "@/plane-web/components/projects/header";
|
||||
import { ProjectsListMobileHeader } from "@/plane-web/components/projects/mobile-header";
|
||||
export default function ProjectListLayout({ children }: { children: ReactNode }) {
|
||||
|
||||
export default function ProjectListLayout() {
|
||||
return (
|
||||
<>
|
||||
<AppHeader header={<ProjectsListHeader />} mobileHeader={<ProjectsListMobileHeader />} />
|
||||
<ContentWrapper>{children}</ContentWrapper>
|
||||
<ContentWrapper>
|
||||
<Outlet />
|
||||
</ContentWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
"use client";
|
||||
|
||||
import type { ReactNode } from "react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { Outlet } from "react-router";
|
||||
// plane web layouts
|
||||
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
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
const { workspaceSlug, projectId } = params;
|
||||
return (
|
||||
<ProjectAuthWrapper workspaceSlug={workspaceSlug?.toString()} projectId={projectId?.toString()}>
|
||||
{children}
|
||||
<ProjectAuthWrapper workspaceSlug={workspaceSlug} projectId={projectId}>
|
||||
<Outlet />
|
||||
</ProjectAuthWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectDetailLayout;
|
||||
}
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
"use client";
|
||||
|
||||
import type { ReactNode } from "react";
|
||||
import { Outlet } from "react-router";
|
||||
// components
|
||||
import { AppHeader } from "@/components/core/app-header";
|
||||
import { ContentWrapper } from "@/components/core/content-wrapper";
|
||||
// local components
|
||||
import { ProjectsListHeader } from "@/plane-web/components/projects/header";
|
||||
import { ProjectsListMobileHeader } from "@/plane-web/components/projects/mobile-header";
|
||||
export default function ProjectListLayout({ children }: { children: ReactNode }) {
|
||||
|
||||
export default function ProjectListLayout() {
|
||||
return (
|
||||
<>
|
||||
<AppHeader header={<ProjectsListHeader />} mobileHeader={<ProjectsListMobileHeader />} />
|
||||
<ContentWrapper>{children}</ContentWrapper>
|
||||
<ContentWrapper>
|
||||
<Outlet />
|
||||
</ContentWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@ import { useTheme } from "next-themes";
|
||||
// plane imports
|
||||
import { HEADER_GITHUB_ICON, GITHUB_REDIRECTED_TRACKER_EVENT } from "@plane/constants";
|
||||
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
|
||||
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 = () => {
|
||||
// plane hooks
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
"use client";
|
||||
|
||||
import { Outlet } from "react-router";
|
||||
import { AppHeader } from "@/components/core/app-header";
|
||||
import { ContentWrapper } from "@/components/core/content-wrapper";
|
||||
import { WorkspaceStickyHeader } from "./header";
|
||||
|
||||
export default function WorkspaceStickiesLayout({ children }: { children: React.ReactNode }) {
|
||||
export default function WorkspaceStickiesLayout() {
|
||||
return (
|
||||
<>
|
||||
<AppHeader header={<WorkspaceStickyHeader />} />
|
||||
<ContentWrapper>{children}</ContentWrapper>
|
||||
<ContentWrapper>
|
||||
<Outlet />
|
||||
</ContentWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// plane imports
|
||||
import { DEFAULT_GLOBAL_VIEWS_LIST } from "@plane/constants";
|
||||
// components
|
||||
@@ -10,10 +9,11 @@ import { PageHead } from "@/components/core/page-title";
|
||||
import { AllIssueLayoutRoot } from "@/components/issues/issue-layouts/roots/all-issue-layout-root";
|
||||
// hooks
|
||||
import { useWorkspace } from "@/hooks/store/use-workspace";
|
||||
import type { Route } from "./+types/page";
|
||||
|
||||
const GlobalViewIssuesPage = observer(() => {
|
||||
function GlobalViewIssuesPage({ params }: Route.ComponentProps) {
|
||||
// router
|
||||
const { globalViewId } = useParams();
|
||||
const { globalViewId } = params;
|
||||
// store hooks
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
// states
|
||||
@@ -31,6 +31,6 @@ const GlobalViewIssuesPage = observer(() => {
|
||||
<AllIssueLayoutRoot isDefaultView={!!defaultView} isLoading={isLoading} toggleLoading={toggleLoading} />
|
||||
</>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default GlobalViewIssuesPage;
|
||||
export default observer(GlobalViewIssuesPage);
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
"use client";
|
||||
|
||||
import { Outlet } from "react-router";
|
||||
import { AppHeader } from "@/components/core/app-header";
|
||||
import { ContentWrapper } from "@/components/core/content-wrapper";
|
||||
import { GlobalIssuesHeader } from "./header";
|
||||
|
||||
export default function GlobalIssuesLayout({ children }: { children: React.ReactNode }) {
|
||||
export default function GlobalIssuesLayout() {
|
||||
return (
|
||||
<>
|
||||
<AppHeader header={<GlobalIssuesHeader />} />
|
||||
<ContentWrapper>{children}</ContentWrapper>
|
||||
<ContentWrapper>
|
||||
<Outlet />
|
||||
</ContentWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import { GlobalViewsList } from "@/components/workspace/views/views-list";
|
||||
// hooks
|
||||
import { useWorkspace } from "@/hooks/store/use-workspace";
|
||||
|
||||
const WorkspaceViewsPage = observer(() => {
|
||||
function WorkspaceViewsPage() {
|
||||
const [query, setQuery] = useState("");
|
||||
// store
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
@@ -47,6 +47,6 @@ const WorkspaceViewsPage = observer(() => {
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default WorkspaceViewsPage;
|
||||
export default observer(WorkspaceViewsPage);
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
"use client";
|
||||
|
||||
import { Outlet } from "react-router";
|
||||
// components
|
||||
import { ContentWrapper } from "@/components/core/content-wrapper";
|
||||
import { ProjectsAppPowerKProvider } from "@/components/power-k/projects-app-provider";
|
||||
import { SettingsHeader } from "@/components/settings/header";
|
||||
import { AuthenticationWrapper } from "@/lib/wrappers/authentication-wrapper";
|
||||
import { WorkspaceAuthWrapper } from "@/plane-web/layouts/workspace-wrapper";
|
||||
|
||||
export default function SettingsLayout({ children }: { children: React.ReactNode }) {
|
||||
export default function SettingsLayout() {
|
||||
return (
|
||||
<AuthenticationWrapper>
|
||||
<WorkspaceAuthWrapper>
|
||||
@@ -17,7 +19,9 @@ export default function SettingsLayout({ children }: { children: React.ReactNode
|
||||
<SettingsHeader />
|
||||
{/* Content */}
|
||||
<ContentWrapper className="p-page-x md:flex w-full">
|
||||
<div className="w-full h-full overflow-hidden">{children}</div>
|
||||
<div className="w-full h-full overflow-hidden">
|
||||
<Outlet />
|
||||
</div>
|
||||
</ContentWrapper>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@@ -12,7 +12,7 @@ import { useUserPermissions } from "@/hooks/store/user";
|
||||
// plane web components
|
||||
import { BillingRoot } from "@/plane-web/components/workspace/billing";
|
||||
|
||||
const BillingSettingsPage = observer(() => {
|
||||
function BillingSettingsPage() {
|
||||
// store hooks
|
||||
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
@@ -30,6 +30,6 @@ const BillingSettingsPage = observer(() => {
|
||||
<BillingRoot />
|
||||
</SettingsContentWrapper>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default BillingSettingsPage;
|
||||
export default observer(BillingSettingsPage);
|
||||
|
||||
@@ -15,7 +15,7 @@ import SettingsHeading from "@/components/settings/heading";
|
||||
import { useWorkspace } from "@/hooks/store/use-workspace";
|
||||
import { useUserPermissions } from "@/hooks/store/user";
|
||||
|
||||
const ExportsPage = observer(() => {
|
||||
function ExportsPage() {
|
||||
// store hooks
|
||||
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
@@ -51,6 +51,6 @@ const ExportsPage = observer(() => {
|
||||
</div>
|
||||
</SettingsContentWrapper>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default ExportsPage;
|
||||
export default observer(ExportsPage);
|
||||
|
||||
@@ -12,7 +12,7 @@ import { SettingsHeading } from "@/components/settings/heading";
|
||||
import { useWorkspace } from "@/hooks/store/use-workspace";
|
||||
import { useUserPermissions } from "@/hooks/store/user";
|
||||
|
||||
const ImportsPage = observer(() => {
|
||||
function ImportsPage() {
|
||||
// router
|
||||
// store hooks
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
@@ -32,6 +32,6 @@ const ImportsPage = observer(() => {
|
||||
</section>
|
||||
</SettingsContentWrapper>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default ImportsPage;
|
||||
export default observer(ImportsPage);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"use client";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
// components
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
@@ -20,9 +19,7 @@ import { IntegrationService } from "@/services/integrations";
|
||||
|
||||
const integrationService = new IntegrationService();
|
||||
|
||||
const WorkspaceIntegrationsPage = observer(() => {
|
||||
// router
|
||||
const { workspaceSlug } = useParams();
|
||||
function WorkspaceIntegrationsPage() {
|
||||
// store hooks
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
@@ -30,8 +27,8 @@ const WorkspaceIntegrationsPage = observer(() => {
|
||||
// derived values
|
||||
const isAdmin = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.WORKSPACE);
|
||||
const pageTitle = currentWorkspace?.name ? `${currentWorkspace.name} - Integrations` : undefined;
|
||||
const { data: appIntegrations } = useSWR(workspaceSlug && isAdmin ? APP_INTEGRATIONS : null, () =>
|
||||
workspaceSlug && isAdmin ? integrationService.getAppIntegrationsList() : null
|
||||
const { data: appIntegrations } = useSWR(isAdmin ? APP_INTEGRATIONS : null, () =>
|
||||
isAdmin ? integrationService.getAppIntegrationsList() : null
|
||||
);
|
||||
|
||||
if (!isAdmin) return <NotAuthorizedView section="settings" className="h-auto" />;
|
||||
@@ -53,6 +50,6 @@ const WorkspaceIntegrationsPage = observer(() => {
|
||||
</section>
|
||||
</SettingsContentWrapper>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default WorkspaceIntegrationsPage;
|
||||
export default observer(WorkspaceIntegrationsPage);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import type { FC, ReactNode } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { Outlet } from "react-router";
|
||||
// constants
|
||||
import { WORKSPACE_SETTINGS_ACCESS } from "@plane/constants";
|
||||
import type { EUserWorkspaceRoles } from "@plane/types";
|
||||
@@ -15,12 +15,7 @@ import { useUserPermissions } from "@/hooks/store/user";
|
||||
// local components
|
||||
import { WorkspaceSettingsSidebar } from "./sidebar";
|
||||
|
||||
export interface IWorkspaceSettingLayout {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const WorkspaceSettingLayout: FC<IWorkspaceSettingLayout> = observer((props) => {
|
||||
const { children } = props;
|
||||
function WorkspaceSettingLayout() {
|
||||
// store hooks
|
||||
const { workspaceUserInfo, getWorkspaceRoleByWorkspaceSlug } = useUserPermissions();
|
||||
// next hooks
|
||||
@@ -46,12 +41,14 @@ const WorkspaceSettingLayout: FC<IWorkspaceSettingLayout> = observer((props) =>
|
||||
) : (
|
||||
<div className="relative flex h-full w-full">
|
||||
<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>
|
||||
</>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default WorkspaceSettingLayout;
|
||||
export default observer(WorkspaceSettingLayout);
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { Search } from "lucide-react";
|
||||
// types
|
||||
import {
|
||||
@@ -32,13 +31,14 @@ import { useUserPermissions } from "@/hooks/store/user";
|
||||
// plane web components
|
||||
import { BillingActionsButton } from "@/plane-web/components/workspace/billing/billing-actions-button";
|
||||
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
|
||||
const [inviteModal, setInviteModal] = useState(false);
|
||||
const [searchQuery, setSearchQuery] = useState<string>("");
|
||||
// router
|
||||
const { workspaceSlug } = useParams();
|
||||
const { workspaceSlug } = params;
|
||||
// store hooks
|
||||
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
|
||||
const {
|
||||
@@ -54,39 +54,42 @@ const WorkspaceMembersSettingsPage = observer(() => {
|
||||
EUserPermissionsLevel.WORKSPACE
|
||||
);
|
||||
|
||||
const handleWorkspaceInvite = (data: IWorkspaceBulkInviteFormData) => {
|
||||
if (!workspaceSlug) return;
|
||||
const handleWorkspaceInvite = async (data: IWorkspaceBulkInviteFormData) => {
|
||||
try {
|
||||
await inviteMembersToWorkspace(workspaceSlug, data);
|
||||
|
||||
return inviteMembersToWorkspace(workspaceSlug.toString(), data)
|
||||
.then(() => {
|
||||
setInviteModal(false);
|
||||
captureSuccess({
|
||||
eventName: MEMBER_TRACKER_EVENTS.invite,
|
||||
payload: {
|
||||
emails: [...data.emails.map((email) => email.email)],
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Success!",
|
||||
message: t("workspace_settings.settings.members.invitations_sent_successfully"),
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
captureError({
|
||||
eventName: MEMBER_TRACKER_EVENTS.invite,
|
||||
payload: {
|
||||
emails: [...data.emails.map((email) => email.email)],
|
||||
},
|
||||
error: err,
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
message: `${err.error ?? t("something_went_wrong_please_try_again")}`,
|
||||
});
|
||||
throw err;
|
||||
setInviteModal(false);
|
||||
|
||||
captureSuccess({
|
||||
eventName: MEMBER_TRACKER_EVENTS.invite,
|
||||
payload: {
|
||||
emails: data.emails.map((email) => email.email),
|
||||
},
|
||||
});
|
||||
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Success!",
|
||||
message: t("workspace_settings.settings.members.invitations_sent_successfully"),
|
||||
});
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (err: any) {
|
||||
captureError({
|
||||
eventName: MEMBER_TRACKER_EVENTS.invite,
|
||||
payload: {
|
||||
emails: data.emails.map((email) => email.email),
|
||||
},
|
||||
error: err,
|
||||
});
|
||||
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
message: `${err.error ?? t("something_went_wrong_please_try_again")}`,
|
||||
});
|
||||
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
// Handler for role filter updates
|
||||
@@ -162,6 +165,6 @@ const WorkspaceMembersSettingsPage = observer(() => {
|
||||
</section>
|
||||
</SettingsContentWrapper>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default WorkspaceMembersSettingsPage;
|
||||
export default observer(WorkspaceMembersSettingsPage);
|
||||
|
||||
@@ -10,7 +10,7 @@ import { WorkspaceDetails } from "@/components/workspace/settings/workspace-deta
|
||||
// hooks
|
||||
import { useWorkspace } from "@/hooks/store/use-workspace";
|
||||
|
||||
const WorkspaceSettingsPage = observer(() => {
|
||||
function WorkspaceSettingsPage() {
|
||||
// store hooks
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { t } = useTranslation();
|
||||
@@ -25,6 +25,6 @@ const WorkspaceSettingsPage = observer(() => {
|
||||
<WorkspaceDetails />
|
||||
</SettingsContentWrapper>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default WorkspaceSettingsPage;
|
||||
export default observer(WorkspaceSettingsPage);
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
import { EUserPermissions, EUserPermissionsLevel, WORKSPACE_SETTINGS_TRACKER_EVENTS } from "@plane/constants";
|
||||
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 { useWorkspace } from "@/hooks/store/use-workspace";
|
||||
import { useUserPermissions } from "@/hooks/store/user";
|
||||
import type { Route } from "./+types/page";
|
||||
|
||||
const WebhookDetailsPage = observer(() => {
|
||||
function WebhookDetailsPage({ params }: Route.ComponentProps) {
|
||||
// states
|
||||
const [deleteWebhookModal, setDeleteWebhookModal] = useState(false);
|
||||
// router
|
||||
const { workspaceSlug, webhookId } = useParams();
|
||||
const { workspaceSlug, webhookId } = params;
|
||||
// mobx store
|
||||
const { currentWebhook, fetchWebhookById, updateWebhook } = useWebhook();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
@@ -33,57 +33,55 @@ const WebhookDetailsPage = observer(() => {
|
||||
// useEffect(() => {
|
||||
// if (isCreated !== "true") clearSecretKey();
|
||||
// }, [clearSecretKey, isCreated]);
|
||||
|
||||
// derived values
|
||||
const isAdmin = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.WORKSPACE);
|
||||
const pageTitle = currentWorkspace?.name ? `${currentWorkspace.name} - Webhook` : undefined;
|
||||
|
||||
useSWR(
|
||||
workspaceSlug && webhookId && isAdmin ? `WEBHOOK_DETAILS_${workspaceSlug}_${webhookId}` : null,
|
||||
workspaceSlug && webhookId && isAdmin
|
||||
? () => fetchWebhookById(workspaceSlug.toString(), webhookId.toString())
|
||||
: null
|
||||
isAdmin ? `WEBHOOK_DETAILS_${workspaceSlug}_${webhookId}` : null,
|
||||
isAdmin ? () => fetchWebhookById(workspaceSlug, webhookId) : null
|
||||
);
|
||||
|
||||
const handleUpdateWebhook = async (formData: IWebhook) => {
|
||||
if (!workspaceSlug || !formData || !formData.id) return;
|
||||
if (!formData || !formData.id) return;
|
||||
|
||||
const payload = {
|
||||
url: formData?.url,
|
||||
is_active: formData?.is_active,
|
||||
project: formData?.project,
|
||||
cycle: formData?.cycle,
|
||||
module: formData?.module,
|
||||
issue: formData?.issue,
|
||||
issue_comment: formData?.issue_comment,
|
||||
url: formData.url,
|
||||
is_active: formData.is_active,
|
||||
project: formData.project,
|
||||
cycle: formData.cycle,
|
||||
module: formData.module,
|
||||
issue: formData.issue,
|
||||
issue_comment: formData.issue_comment,
|
||||
};
|
||||
await updateWebhook(workspaceSlug.toString(), formData.id, payload)
|
||||
.then(() => {
|
||||
captureSuccess({
|
||||
eventName: WORKSPACE_SETTINGS_TRACKER_EVENTS.webhook_updated,
|
||||
payload: {
|
||||
webhook: formData.id,
|
||||
},
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Success!",
|
||||
message: "Webhook updated successfully.",
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
captureError({
|
||||
eventName: WORKSPACE_SETTINGS_TRACKER_EVENTS.webhook_updated,
|
||||
payload: {
|
||||
webhook: formData.id,
|
||||
},
|
||||
error: error as Error,
|
||||
});
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
message: error?.error ?? "Something went wrong. Please try again.",
|
||||
});
|
||||
|
||||
try {
|
||||
await updateWebhook(workspaceSlug, formData.id, payload);
|
||||
|
||||
captureSuccess({
|
||||
eventName: WORKSPACE_SETTINGS_TRACKER_EVENTS.webhook_updated,
|
||||
payload: { webhook: formData.id },
|
||||
});
|
||||
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Success!",
|
||||
message: "Webhook updated successfully.",
|
||||
});
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (error: any) {
|
||||
captureError({
|
||||
eventName: WORKSPACE_SETTINGS_TRACKER_EVENTS.webhook_updated,
|
||||
payload: { webhook: formData.id },
|
||||
error: error as Error,
|
||||
});
|
||||
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
message: error?.error ?? "Something went wrong. Please try again.",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (!isAdmin)
|
||||
@@ -108,13 +106,13 @@ const WebhookDetailsPage = observer(() => {
|
||||
<PageHead title={pageTitle} />
|
||||
<DeleteWebhookModal isOpen={deleteWebhookModal} onClose={() => setDeleteWebhookModal(false)} />
|
||||
<div className="w-full space-y-8 overflow-y-auto">
|
||||
<div className="">
|
||||
<WebhookForm onSubmit={async (data) => await handleUpdateWebhook(data)} data={currentWebhook} />
|
||||
<div>
|
||||
<WebhookForm onSubmit={handleUpdateWebhook} data={currentWebhook} />
|
||||
</div>
|
||||
{currentWebhook && <WebhookDeleteSection openDeleteModal={() => setDeleteWebhookModal(true)} />}
|
||||
</div>
|
||||
</SettingsContentWrapper>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default WebhookDetailsPage;
|
||||
export default observer(WebhookDetailsPage);
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
// plane imports
|
||||
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 { useWorkspace } from "@/hooks/store/use-workspace";
|
||||
import { useUserPermissions } from "@/hooks/store/user";
|
||||
import type { Route } from "./+types/page";
|
||||
|
||||
const WebhooksListPage = observer(() => {
|
||||
function WebhooksListPage({ params }: Route.ComponentProps) {
|
||||
// states
|
||||
const [showCreateWebhookModal, setShowCreateWebhookModal] = useState(false);
|
||||
// router
|
||||
const { workspaceSlug } = useParams();
|
||||
const { workspaceSlug } = params;
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// mobx store
|
||||
@@ -36,8 +36,8 @@ const WebhooksListPage = observer(() => {
|
||||
const canPerformWorkspaceAdminActions = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.WORKSPACE);
|
||||
|
||||
useSWR(
|
||||
workspaceSlug && canPerformWorkspaceAdminActions ? `WEBHOOKS_LIST_${workspaceSlug}` : null,
|
||||
workspaceSlug && canPerformWorkspaceAdminActions ? () => fetchWebhooks(workspaceSlug.toString()) : null
|
||||
canPerformWorkspaceAdminActions ? `WEBHOOKS_LIST_${workspaceSlug}` : null,
|
||||
canPerformWorkspaceAdminActions ? () => fetchWebhooks(workspaceSlug) : null
|
||||
);
|
||||
|
||||
const pageTitle = currentWorkspace?.name
|
||||
@@ -112,6 +112,6 @@ const WebhooksListPage = observer(() => {
|
||||
</div>
|
||||
</SettingsContentWrapper>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default WebhooksListPage;
|
||||
export default observer(WebhooksListPage);
|
||||
|
||||
@@ -2,29 +2,34 @@
|
||||
|
||||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useTheme } from "next-themes";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// ui
|
||||
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
|
||||
import { PageHead } from "@/components/core/page-title";
|
||||
import { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root";
|
||||
import { ProfileActivityListPage } from "@/components/profile/activity/profile-activity-list";
|
||||
// hooks
|
||||
import { SettingsHeading } from "@/components/settings/heading";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
|
||||
const PER_PAGE = 100;
|
||||
|
||||
const ProfileActivityPage = observer(() => {
|
||||
function ProfileActivityPage() {
|
||||
// states
|
||||
const [pageCount, setPageCount] = useState(1);
|
||||
const [totalPages, setTotalPages] = useState(0);
|
||||
const [resultsCount, setResultsCount] = useState(0);
|
||||
const [isEmpty, setIsEmpty] = useState(false);
|
||||
// theme hook
|
||||
const { resolvedTheme } = useTheme();
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// derived values
|
||||
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/profile/activity" });
|
||||
const resolvedPath = resolvedTheme === "light" ? lightActivityAsset : darkActivityAsset;
|
||||
|
||||
const updateTotalPages = (count: number) => setTotalPages(count);
|
||||
|
||||
@@ -84,6 +89,6 @@ const ProfileActivityPage = observer(() => {
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default ProfileActivityPage;
|
||||
export default observer(ProfileActivityPage);
|
||||
|
||||
@@ -21,7 +21,7 @@ import { useWorkspace } from "@/hooks/store/use-workspace";
|
||||
|
||||
const apiTokenService = new APITokenService();
|
||||
|
||||
const ApiTokensPage = observer(() => {
|
||||
function ApiTokensPage() {
|
||||
// states
|
||||
const [isCreateTokenModalOpen, setIsCreateTokenModalOpen] = useState(false);
|
||||
// router
|
||||
@@ -106,6 +106,6 @@ const ApiTokensPage = observer(() => {
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default ApiTokensPage;
|
||||
export default observer(ApiTokensPage);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import type { ReactNode } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { Outlet } from "react-router";
|
||||
// components
|
||||
import { SettingsContentWrapper } from "@/components/settings/content-wrapper";
|
||||
import { getProfileActivePath } from "@/components/settings/helper";
|
||||
@@ -10,12 +10,7 @@ import { SettingsMobileNav } from "@/components/settings/mobile";
|
||||
// local imports
|
||||
import { ProfileSidebar } from "./sidebar";
|
||||
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
const ProfileSettingsLayout = observer((props: Props) => {
|
||||
const { children } = props;
|
||||
function ProfileSettingsLayout() {
|
||||
// router
|
||||
const pathname = usePathname();
|
||||
|
||||
@@ -27,11 +22,13 @@ const ProfileSettingsLayout = observer((props: Props) => {
|
||||
<ProfileSidebar />
|
||||
</div>
|
||||
<div className="w-full h-full overflow-y-scroll md:pt-page-y">
|
||||
<SettingsContentWrapper>{children}</SettingsContentWrapper>
|
||||
<SettingsContentWrapper>
|
||||
<Outlet />
|
||||
</SettingsContentWrapper>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default ProfileSettingsLayout;
|
||||
export default observer(ProfileSettingsLayout);
|
||||
|
||||
@@ -9,7 +9,7 @@ import { ProfileForm } from "@/components/profile/form";
|
||||
// hooks
|
||||
import { useUser } from "@/hooks/store/user";
|
||||
|
||||
const ProfileSettingsPage = observer(() => {
|
||||
function ProfileSettingsPage() {
|
||||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
const { data: currentUser, userProfile } = useUser();
|
||||
@@ -27,6 +27,6 @@ const ProfileSettingsPage = observer(() => {
|
||||
<ProfileForm user={currentUser} profile={userProfile.data} />
|
||||
</>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default ProfileSettingsPage;
|
||||
export default observer(ProfileSettingsPage);
|
||||
|
||||
@@ -13,7 +13,7 @@ import { SettingsHeading } from "@/components/settings/heading";
|
||||
// hooks
|
||||
import { useUserProfile } from "@/hooks/store/user";
|
||||
|
||||
const ProfileAppearancePage = observer(() => {
|
||||
function ProfileAppearancePage() {
|
||||
const { t } = useTranslation();
|
||||
// hooks
|
||||
const { data: userProfile } = useUserProfile();
|
||||
@@ -44,6 +44,6 @@ const ProfileAppearancePage = observer(() => {
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default ProfileAppearancePage;
|
||||
export default observer(ProfileAppearancePage);
|
||||
|
||||
@@ -42,7 +42,7 @@ const defaultShowPassword = {
|
||||
confirmPassword: false,
|
||||
};
|
||||
|
||||
const SecurityPage = observer(() => {
|
||||
function SecurityPage() {
|
||||
// store
|
||||
const { data: currentUser, changePassword } = useUser();
|
||||
// states
|
||||
@@ -255,6 +255,6 @@ const SecurityPage = observer(() => {
|
||||
</form>
|
||||
</>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default SecurityPage;
|
||||
export default observer(SecurityPage);
|
||||
|
||||
@@ -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);
|
||||
@@ -1,8 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
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";
|
||||
// plane web imports
|
||||
import { CustomAutomationsRoot } from "@/plane-web/components/automations/root";
|
||||
import type { Route } from "./+types/page";
|
||||
|
||||
const AutomationSettingsPage = observer(() => {
|
||||
function AutomationSettingsPage({ params }: Route.ComponentProps) {
|
||||
// router
|
||||
const { workspaceSlug: workspaceSlugParam, projectId: projectIdParam } = useParams();
|
||||
const workspaceSlug = workspaceSlugParam?.toString();
|
||||
const projectId = projectIdParam?.toString();
|
||||
const { workspaceSlug, projectId } = params;
|
||||
// store hooks
|
||||
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
|
||||
const { currentProjectDetails: projectDetails, updateProject } = useProject();
|
||||
@@ -35,15 +32,17 @@ const AutomationSettingsPage = observer(() => {
|
||||
const canPerformProjectAdminActions = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT);
|
||||
|
||||
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({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
message: "Something went wrong. Please try again.",
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// derived values
|
||||
@@ -67,6 +66,6 @@ const AutomationSettingsPage = observer(() => {
|
||||
<CustomAutomationsRoot projectId={projectId} workspaceSlug={workspaceSlug} />
|
||||
</SettingsContentWrapper>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default AutomationSettingsPage;
|
||||
export default observer(AutomationSettingsPage);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// components
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
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 { useProject } from "@/hooks/store/use-project";
|
||||
import { useUserPermissions } from "@/hooks/store/user";
|
||||
import type { Route } from "./+types/page";
|
||||
|
||||
const EstimatesSettingsPage = observer(() => {
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
function EstimatesSettingsPage({ params }: Route.ComponentProps) {
|
||||
const { workspaceSlug, projectId } = params;
|
||||
// store
|
||||
const { currentProjectDetails } = useProject();
|
||||
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
|
||||
@@ -22,8 +22,6 @@ const EstimatesSettingsPage = observer(() => {
|
||||
const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - Estimates` : undefined;
|
||||
const canPerformProjectAdminActions = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT);
|
||||
|
||||
if (!workspaceSlug || !projectId) return <></>;
|
||||
|
||||
if (workspaceUserInfo && !canPerformProjectAdminActions) {
|
||||
return <NotAuthorizedView section="settings" isProjectView className="h-auto" />;
|
||||
}
|
||||
@@ -32,14 +30,10 @@ const EstimatesSettingsPage = observer(() => {
|
||||
<SettingsContentWrapper>
|
||||
<PageHead title={pageTitle} />
|
||||
<div className={`w-full ${canPerformProjectAdminActions ? "" : "pointer-events-none opacity-60"}`}>
|
||||
<EstimateRoot
|
||||
workspaceSlug={workspaceSlug?.toString()}
|
||||
projectId={projectId?.toString()}
|
||||
isAdmin={canPerformProjectAdminActions}
|
||||
/>
|
||||
<EstimateRoot workspaceSlug={workspaceSlug} projectId={projectId} isAdmin={canPerformProjectAdminActions} />
|
||||
</div>
|
||||
</SettingsContentWrapper>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default EstimatesSettingsPage;
|
||||
export default observer(EstimatesSettingsPage);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// components
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
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 { useUserPermissions } from "@/hooks/store/user";
|
||||
import { ProjectFeaturesList } from "@/plane-web/components/projects/settings/features-list";
|
||||
import type { Route } from "./+types/page";
|
||||
|
||||
const FeaturesSettingsPage = observer(() => {
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
function FeaturesSettingsPage({ params }: Route.ComponentProps) {
|
||||
const { workspaceSlug, projectId } = params;
|
||||
// store
|
||||
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
|
||||
|
||||
@@ -22,8 +22,6 @@ const FeaturesSettingsPage = observer(() => {
|
||||
const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - Features` : undefined;
|
||||
const canPerformProjectAdminActions = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT);
|
||||
|
||||
if (!workspaceSlug || !projectId) return null;
|
||||
|
||||
if (workspaceUserInfo && !canPerformProjectAdminActions) {
|
||||
return <NotAuthorizedView section="settings" isProjectView className="h-auto" />;
|
||||
}
|
||||
@@ -33,13 +31,13 @@ const FeaturesSettingsPage = observer(() => {
|
||||
<PageHead title={pageTitle} />
|
||||
<section className={`w-full ${canPerformProjectAdminActions ? "" : "opacity-60"}`}>
|
||||
<ProjectFeaturesList
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
projectId={projectId.toString()}
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
isAdmin={canPerformProjectAdminActions}
|
||||
/>
|
||||
</section>
|
||||
</SettingsContentWrapper>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default FeaturesSettingsPage;
|
||||
export default observer(FeaturesSettingsPage);
|
||||
|
||||
@@ -14,7 +14,7 @@ import { SettingsContentWrapper } from "@/components/settings/content-wrapper";
|
||||
import { useProject } from "@/hooks/store/use-project";
|
||||
import { useUserPermissions } from "@/hooks/store/user";
|
||||
|
||||
const LabelsSettingsPage = observer(() => {
|
||||
function LabelsSettingsPage() {
|
||||
// store hooks
|
||||
const { currentProjectDetails } = useProject();
|
||||
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
|
||||
@@ -54,6 +54,6 @@ const LabelsSettingsPage = observer(() => {
|
||||
</div>
|
||||
</SettingsContentWrapper>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default LabelsSettingsPage;
|
||||
export default observer(LabelsSettingsPage);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// plane imports
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
@@ -18,18 +17,17 @@ import { useUserPermissions } from "@/hooks/store/user";
|
||||
// plane web imports
|
||||
import { ProjectTeamspaceList } from "@/plane-web/components/projects/teamspaces/teamspace-list";
|
||||
import { getProjectSettingsPageLabelI18nKey } from "@/plane-web/helpers/project-settings";
|
||||
import type { Route } from "./+types/page";
|
||||
|
||||
const MembersSettingsPage = observer(() => {
|
||||
function MembersSettingsPage({ params }: Route.ComponentProps) {
|
||||
// router
|
||||
const { workspaceSlug: routerWorkspaceSlug, projectId: routerProjectId } = useParams();
|
||||
const { workspaceSlug, projectId } = params;
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
const { currentProjectDetails } = useProject();
|
||||
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
|
||||
// derived values
|
||||
const projectId = routerProjectId?.toString();
|
||||
const workspaceSlug = routerWorkspaceSlug?.toString();
|
||||
const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - Members` : undefined;
|
||||
const isProjectMemberOrAdmin = allowPermissions(
|
||||
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
||||
@@ -51,6 +49,6 @@ const MembersSettingsPage = observer(() => {
|
||||
<ProjectMemberList projectId={projectId} workspaceSlug={workspaceSlug} />
|
||||
</SettingsContentWrapper>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default MembersSettingsPage;
|
||||
export default observer(MembersSettingsPage);
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
// plane imports
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
@@ -18,41 +17,34 @@ import { SettingsContentWrapper } from "@/components/settings/content-wrapper";
|
||||
// hooks
|
||||
import { useProject } from "@/hooks/store/use-project";
|
||||
import { useUserPermissions } from "@/hooks/store/user";
|
||||
import type { Route } from "./+types/page";
|
||||
|
||||
const ProjectSettingsPage = observer(() => {
|
||||
function ProjectSettingsPage({ params }: Route.ComponentProps) {
|
||||
// states
|
||||
const [selectProject, setSelectedProject] = useState<string | null>(null);
|
||||
const [archiveProject, setArchiveProject] = useState<boolean>(false);
|
||||
// router
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
const { workspaceSlug, projectId } = params;
|
||||
// store hooks
|
||||
const { currentProjectDetails, fetchProjectDetails } = useProject();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
|
||||
// api call to fetch project details
|
||||
// TODO: removed this API if not necessary
|
||||
const { isLoading } = useSWR(
|
||||
workspaceSlug && projectId ? `PROJECT_DETAILS_${projectId}` : null,
|
||||
workspaceSlug && projectId ? () => fetchProjectDetails(workspaceSlug.toString(), projectId.toString()) : null
|
||||
);
|
||||
const { isLoading } = useSWR(`PROJECT_DETAILS_${projectId}`, () => fetchProjectDetails(workspaceSlug, projectId));
|
||||
// derived values
|
||||
const isAdmin = allowPermissions(
|
||||
[EUserPermissions.ADMIN],
|
||||
EUserPermissionsLevel.PROJECT,
|
||||
workspaceSlug.toString(),
|
||||
projectId.toString()
|
||||
);
|
||||
const isAdmin = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT, workspaceSlug, projectId);
|
||||
|
||||
const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - General Settings` : undefined;
|
||||
|
||||
return (
|
||||
<SettingsContentWrapper>
|
||||
<PageHead title={pageTitle} />
|
||||
{currentProjectDetails && workspaceSlug && projectId && (
|
||||
{currentProjectDetails && (
|
||||
<>
|
||||
<ArchiveRestoreProjectModal
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
projectId={projectId.toString()}
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
isOpen={archiveProject}
|
||||
onClose={() => setArchiveProject(false)}
|
||||
archive
|
||||
@@ -66,11 +58,11 @@ const ProjectSettingsPage = observer(() => {
|
||||
)}
|
||||
|
||||
<div className={`w-full ${isAdmin ? "" : "opacity-60"}`}>
|
||||
{currentProjectDetails && workspaceSlug && projectId && !isLoading ? (
|
||||
{currentProjectDetails && !isLoading ? (
|
||||
<ProjectDetailsForm
|
||||
project={currentProjectDetails}
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
projectId={projectId.toString()}
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
isAdmin={isAdmin}
|
||||
/>
|
||||
) : (
|
||||
@@ -92,6 +84,6 @@ const ProjectSettingsPage = observer(() => {
|
||||
</div>
|
||||
</SettingsContentWrapper>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default ProjectSettingsPage;
|
||||
export default observer(ProjectSettingsPage);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// components
|
||||
@@ -13,9 +12,10 @@ import { SettingsContentWrapper } from "@/components/settings/content-wrapper";
|
||||
import { SettingsHeading } from "@/components/settings/heading";
|
||||
import { useProject } from "@/hooks/store/use-project";
|
||||
import { useUserPermissions } from "@/hooks/store/user";
|
||||
import type { Route } from "./+types/page";
|
||||
|
||||
const StatesSettingsPage = observer(() => {
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
function StatesSettingsPage({ params }: Route.ComponentProps) {
|
||||
const { workspaceSlug, projectId } = params;
|
||||
// store
|
||||
const { currentProjectDetails } = useProject();
|
||||
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
|
||||
@@ -42,12 +42,10 @@ const StatesSettingsPage = observer(() => {
|
||||
title={t("project_settings.states.heading")}
|
||||
description={t("project_settings.states.description")}
|
||||
/>
|
||||
{workspaceSlug && projectId && (
|
||||
<ProjectStateRoot workspaceSlug={workspaceSlug.toString()} projectId={projectId.toString()} />
|
||||
)}
|
||||
<ProjectStateRoot workspaceSlug={workspaceSlug} projectId={projectId} />
|
||||
</div>
|
||||
</SettingsContentWrapper>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default StatesSettingsPage;
|
||||
export default observer(StatesSettingsPage);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import type { ReactNode } from "react";
|
||||
import { useEffect } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { Outlet } from "react-router";
|
||||
// components
|
||||
import { getProjectActivePath } from "@/components/settings/helper";
|
||||
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 { useAppRouter } from "@/hooks/use-app-router";
|
||||
import { ProjectAuthWrapper } from "@/plane-web/layouts/project-wrapper";
|
||||
import type { Route } from "./+types/layout";
|
||||
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
const ProjectSettingsLayout = observer((props: Props) => {
|
||||
const { children } = props;
|
||||
function ProjectSettingsLayout({ params }: Route.ComponentProps) {
|
||||
// router
|
||||
const router = useAppRouter();
|
||||
const pathname = usePathname();
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
const { workspaceSlug, projectId } = params;
|
||||
const { joinedProjectIds } = useProject();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -34,14 +30,16 @@ const ProjectSettingsLayout = observer((props: Props) => {
|
||||
return (
|
||||
<>
|
||||
<SettingsMobileNav hamburgerContent={ProjectSettingsSidebar} activePath={getProjectActivePath(pathname) || ""} />
|
||||
<ProjectAuthWrapper workspaceSlug={workspaceSlug?.toString()} projectId={projectId?.toString()}>
|
||||
<ProjectAuthWrapper workspaceSlug={workspaceSlug} projectId={projectId}>
|
||||
<div className="relative flex h-full w-full">
|
||||
<div className="hidden md:block">{projectId && <ProjectSettingsSidebar />}</div>
|
||||
<div className="w-full h-full overflow-y-scroll md:pt-page-y">{children}</div>
|
||||
<div className="w-full h-full overflow-y-scroll md:pt-page-y">
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
</ProjectAuthWrapper>
|
||||
</>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default ProjectSettingsLayout;
|
||||
export default observer(ProjectSettingsLayout);
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import { Outlet } from "react-router";
|
||||
import { AppRailProvider } from "@/hooks/context/app-rail-context";
|
||||
import { WorkspaceContentWrapper } from "@/plane-web/components/workspace/content-wrapper";
|
||||
|
||||
export default function WorkspaceLayout({ children }: { children: React.ReactNode }) {
|
||||
export default function WorkspaceLayout() {
|
||||
return (
|
||||
<AppRailProvider>
|
||||
<WorkspaceContentWrapper>{children}</WorkspaceContentWrapper>
|
||||
<WorkspaceContentWrapper>
|
||||
<Outlet />
|
||||
</WorkspaceContentWrapper>
|
||||
</AppRailProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Outlet } from "react-router";
|
||||
import type { Route } from "./+types/layout";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Forgot Password - Plane",
|
||||
};
|
||||
|
||||
export default function ForgotPasswordLayout({ children }: { children: React.ReactNode }) {
|
||||
return children;
|
||||
export default function ForgotPasswordLayout() {
|
||||
return <Outlet />;
|
||||
}
|
||||
|
||||
export const meta: Route.MetaFunction = () => [{ title: "Forgot Password - Plane" }];
|
||||
|
||||
@@ -10,15 +10,17 @@ import { EAuthModes, EPageTypes } from "@/helpers/authentication.helper";
|
||||
import DefaultLayout from "@/layouts/default-layout";
|
||||
import { AuthenticationWrapper } from "@/lib/wrappers/authentication-wrapper";
|
||||
|
||||
const ForgotPasswordPage = observer(() => (
|
||||
<DefaultLayout>
|
||||
<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">
|
||||
<AuthHeader type={EAuthModes.SIGN_IN} />
|
||||
<ForgotPasswordForm />
|
||||
</div>
|
||||
</AuthenticationWrapper>
|
||||
</DefaultLayout>
|
||||
));
|
||||
function ForgotPasswordPage() {
|
||||
return (
|
||||
<DefaultLayout>
|
||||
<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">
|
||||
<AuthHeader type={EAuthModes.SIGN_IN} />
|
||||
<ForgotPasswordForm />
|
||||
</div>
|
||||
</AuthenticationWrapper>
|
||||
</DefaultLayout>
|
||||
);
|
||||
}
|
||||
|
||||
export default ForgotPasswordPage;
|
||||
export default observer(ForgotPasswordPage);
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Outlet } from "react-router";
|
||||
import type { Route } from "./+types/layout";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Reset Password - Plane",
|
||||
};
|
||||
|
||||
export default function ResetPasswordLayout({ children }: { children: React.ReactNode }) {
|
||||
return children;
|
||||
export default function ResetPasswordLayout() {
|
||||
return <Outlet />;
|
||||
}
|
||||
|
||||
export const meta: Route.MetaFunction = () => [{ title: "Reset Password - Plane" }];
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Outlet } from "react-router";
|
||||
import type { Route } from "./+types/layout";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Set Password - Plane",
|
||||
};
|
||||
|
||||
export default function SetPasswordLayout({ children }: { children: React.ReactNode }) {
|
||||
return children;
|
||||
export default function SetPasswordLayout() {
|
||||
return <Outlet />;
|
||||
}
|
||||
|
||||
export const meta: Route.MetaFunction = () => [{ title: "Set Password - Plane" }];
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Outlet } from "react-router";
|
||||
import type { Route } from "./+types/layout";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Workspace",
|
||||
};
|
||||
|
||||
export default function CreateWorkspaceLayout({ children }: { children: React.ReactNode }) {
|
||||
return children;
|
||||
export default function CreateWorkspaceLayout() {
|
||||
return <Outlet />;
|
||||
}
|
||||
|
||||
export const meta: Route.MetaFunction = () => [{ title: "Create Workspace" }];
|
||||
|
||||
@@ -9,6 +9,8 @@ import { useTranslation } from "@plane/i18n";
|
||||
import { Button, getButtonStyling } from "@plane/propel/button";
|
||||
import { PlaneLogo } from "@plane/propel/icons";
|
||||
import type { IWorkspace } from "@plane/types";
|
||||
// assets
|
||||
import WorkspaceCreationDisabled from "@/app/assets/workspace/workspace-creation-disabled.png?url";
|
||||
// components
|
||||
import { CreateWorkspaceForm } from "@/components/workspace/create-workspace-form";
|
||||
// hooks
|
||||
@@ -18,10 +20,8 @@ import { useAppRouter } from "@/hooks/use-app-router";
|
||||
import { AuthenticationWrapper } from "@/lib/wrappers/authentication-wrapper";
|
||||
// plane web helpers
|
||||
import { getIsWorkspaceCreationDisabled } from "@/plane-web/helpers/instance.helper";
|
||||
// images
|
||||
import WorkspaceCreationDisabled from "@/public/workspace/workspace-creation-disabled.png";
|
||||
|
||||
const CreateWorkspacePage = observer(() => {
|
||||
function CreateWorkspacePage() {
|
||||
const { t } = useTranslation();
|
||||
// router
|
||||
const router = useAppRouter();
|
||||
@@ -103,6 +103,6 @@ const CreateWorkspacePage = observer(() => {
|
||||
</div>
|
||||
</AuthenticationWrapper>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default CreateWorkspacePage;
|
||||
export default observer(CreateWorkspacePage);
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
export default function InstallationProviderLayout({ children }: { children: React.ReactNode }) {
|
||||
return children;
|
||||
import { Outlet } from "react-router";
|
||||
import type { Route } from "./+types/layout";
|
||||
|
||||
export default function InstallationProviderLayout() {
|
||||
return <Outlet />;
|
||||
}
|
||||
|
||||
export const meta: Route.MetaFunction = () => [{ title: "Installations" }];
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
"use client";
|
||||
|
||||
import React, { useEffect } from "react";
|
||||
import { useParams, useSearchParams } from "next/navigation";
|
||||
import { useEffect } from "react";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
// ui
|
||||
import { LogoSpinner } from "@/components/common/logo-spinner";
|
||||
// services
|
||||
import { AppInstallationService } from "@/services/app_installation.service";
|
||||
import type { Route } from "./+types/page";
|
||||
|
||||
// services
|
||||
const appInstallationService = new AppInstallationService();
|
||||
|
||||
export default function AppPostInstallation() {
|
||||
export default function AppPostInstallation({ params }: Route.ComponentProps) {
|
||||
// params
|
||||
const { provider } = useParams();
|
||||
const { provider } = params;
|
||||
// query params
|
||||
const searchParams = useSearchParams();
|
||||
const installation_id = searchParams.get("installation_id");
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Outlet } from "react-router";
|
||||
import type { Route } from "./+types/layout";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Invitations",
|
||||
};
|
||||
|
||||
export default function InvitationsLayout({ children }: { children: React.ReactNode }) {
|
||||
return children;
|
||||
export default function InvitationsLayout() {
|
||||
return <Outlet />;
|
||||
}
|
||||
|
||||
export const meta: Route.MetaFunction = () => [{ title: "Invitations" }];
|
||||
|
||||
@@ -15,6 +15,8 @@ import { PlaneLogo } from "@plane/propel/icons";
|
||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||
import type { IWorkspaceMemberInvitation } from "@plane/types";
|
||||
import { truncateText } from "@plane/utils";
|
||||
// assets
|
||||
import emptyInvitation from "@/app/assets/empty-state/invitation.svg?url";
|
||||
// components
|
||||
import { EmptyState } from "@/components/common/empty-state";
|
||||
import { WorkspaceLogo } from "@/components/workspace/logo";
|
||||
@@ -29,12 +31,10 @@ import { useAppRouter } from "@/hooks/use-app-router";
|
||||
import { AuthenticationWrapper } from "@/lib/wrappers/authentication-wrapper";
|
||||
// plane web services
|
||||
import { WorkspaceService } from "@/plane-web/services";
|
||||
// images
|
||||
import emptyInvitation from "@/public/empty-state/invitation.svg";
|
||||
|
||||
const workspaceService = new WorkspaceService();
|
||||
|
||||
const UserInvitationsPage = observer(() => {
|
||||
function UserInvitationsPage() {
|
||||
// states
|
||||
const [invitationsRespond, setInvitationsRespond] = useState<string[]>([]);
|
||||
const [isJoiningWorkspaces, setIsJoiningWorkspaces] = useState(false);
|
||||
@@ -220,6 +220,6 @@ const UserInvitationsPage = observer(() => {
|
||||
</div>
|
||||
</AuthenticationWrapper>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default UserInvitationsPage;
|
||||
export default observer(UserInvitationsPage);
|
||||
|
||||
@@ -1,28 +1,25 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
|
||||
// TODO: Check if we need this
|
||||
// https://nextjs.org/docs/app/api-reference/functions/generate-metadata#link-relpreload
|
||||
export const usePreloadResources = () => {
|
||||
useEffect(() => {
|
||||
const preloadItem = (url: string) => {
|
||||
ReactDOM.preload(url, { as: "fetch", crossOrigin: "use-credentials" });
|
||||
};
|
||||
// export const usePreloadResources = () => {
|
||||
// useEffect(() => {
|
||||
// const preloadItem = (url: string) => {
|
||||
// ReactDOM.preload(url, { as: "fetch", crossOrigin: "use-credentials" });
|
||||
// };
|
||||
|
||||
const urls = [
|
||||
`${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/profile/`,
|
||||
`${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()}`,
|
||||
];
|
||||
// const urls = [
|
||||
// `${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/profile/`,
|
||||
// `${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()}`,
|
||||
// ];
|
||||
|
||||
urls.forEach((url) => preloadItem(url));
|
||||
}, []);
|
||||
};
|
||||
// urls.forEach((url) => preloadItem(url));
|
||||
// }, []);
|
||||
// };
|
||||
|
||||
export const PreloadResources = () => {
|
||||
usePreloadResources();
|
||||
return null;
|
||||
};
|
||||
export const PreloadResources = () =>
|
||||
// usePreloadResources();
|
||||
null;
|
||||
|
||||
@@ -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";
|
||||
// types
|
||||
|
||||
// styles
|
||||
import "@/styles/power-k.css";
|
||||
import "@/styles/emoji.css";
|
||||
import "@plane/propel/styles/react-day-picker.css";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
robots: {
|
||||
index: false,
|
||||
follow: false,
|
||||
},
|
||||
};
|
||||
export const meta: Route.MetaFunction = () => [
|
||||
{ name: "robots", content: "noindex, nofollow" },
|
||||
{ name: "viewport", content: "width=device-width, initial-scale=1, minimum-scale=1, viewport-fit=cover" },
|
||||
];
|
||||
|
||||
export const viewport: Viewport = {
|
||||
minimumScale: 1,
|
||||
initialScale: 1,
|
||||
width: "device-width",
|
||||
viewportFit: "cover",
|
||||
};
|
||||
|
||||
export default function AppLayout({ children }: { children: React.ReactNode }) {
|
||||
export default function AppLayout() {
|
||||
return (
|
||||
<>
|
||||
<PreloadResources />
|
||||
{children}
|
||||
<Outlet />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user