-
diff --git a/apps/admin/app/(all)/(dashboard)/general/page.tsx b/apps/admin/app/(all)/(dashboard)/general/page.tsx
index fd7efd30998..6e0a86d52a2 100644
--- a/apps/admin/app/(all)/(dashboard)/general/page.tsx
+++ b/apps/admin/app/(all)/(dashboard)/general/page.tsx
@@ -1,30 +1,26 @@
import { observer } from "mobx-react";
+// components
+import { PageWrapper } from "@/components/common/page-wrapper";
// hooks
import { useInstance } from "@/hooks/store";
-// components
-import type { Route } from "./+types/page";
+// local imports
import { GeneralConfigurationForm } from "./form";
+// types
+import type { Route } from "./+types/page";
function GeneralPage() {
const { instance, instanceAdmins } = useInstance();
return (
- <>
-
-
-
General settings
-
- Change the name of your instance and instance admin e-mail addresses. Enable or disable telemetry in your
- instance.
-
-
-
- {instance && instanceAdmins && (
-
- )}
-
-
- >
+
+ {instance && instanceAdmins && }
+
);
}
diff --git a/apps/admin/app/(all)/(dashboard)/image/form.tsx b/apps/admin/app/(all)/(dashboard)/image/form.tsx
index 7d12ee59f7a..154e74bf12a 100644
--- a/apps/admin/app/(all)/(dashboard)/image/form.tsx
+++ b/apps/admin/app/(all)/(dashboard)/image/form.tsx
@@ -71,7 +71,7 @@ export function InstanceImageConfigForm(props: IInstanceImageConfigForm) {
diff --git a/apps/admin/app/(all)/(dashboard)/image/page.tsx b/apps/admin/app/(all)/(dashboard)/image/page.tsx
index 5bd2914d54f..7b89393f270 100644
--- a/apps/admin/app/(all)/(dashboard)/image/page.tsx
+++ b/apps/admin/app/(all)/(dashboard)/image/page.tsx
@@ -1,10 +1,13 @@
import { observer } from "mobx-react";
import useSWR from "swr";
import { Loader } from "@plane/ui";
+// components
+import { PageWrapper } from "@/components/common/page-wrapper";
// hooks
import { useInstance } from "@/hooks/store";
-// local
+// types
import type { Route } from "./+types/page";
+// local
import { InstanceImageConfigForm } from "./form";
const InstanceImagePage = observer(function InstanceImagePage(_props: Route.ComponentProps) {
@@ -14,26 +17,21 @@ const InstanceImagePage = observer(function InstanceImagePage(_props: Route.Comp
useSWR("INSTANCE_CONFIGURATIONS", () => fetchInstanceConfigurations());
return (
- <>
-
-
-
Third-party image libraries
-
- Let your users search and choose images from third-party libraries
-
-
-
- {formattedConfig ? (
-
- ) : (
-
-
-
-
- )}
-
-
- >
+
+ {formattedConfig ? (
+
+ ) : (
+
+
+
+
+ )}
+
);
});
diff --git a/apps/admin/app/(all)/(dashboard)/layout.tsx b/apps/admin/app/(all)/(dashboard)/layout.tsx
index 2798c8f043b..ba5564bf52d 100644
--- a/apps/admin/app/(all)/(dashboard)/layout.tsx
+++ b/apps/admin/app/(all)/(dashboard)/layout.tsx
@@ -3,13 +3,13 @@ import { observer } from "mobx-react";
import { useRouter } from "next/navigation";
import { Outlet } from "react-router";
// components
+import { AdminHeader } from "@/components/common/header";
import { LogoSpinner } from "@/components/common/logo-spinner";
import { NewUserPopup } from "@/components/new-user-popup";
// hooks
import { useUser } from "@/hooks/store";
// local components
import type { Route } from "./+types/layout";
-import { AdminHeader } from "./header";
import { AdminSidebar } from "./sidebar";
function AdminLayout(_props: Route.ComponentProps) {
diff --git a/apps/admin/app/(all)/(dashboard)/sidebar-dropdown.tsx b/apps/admin/app/(all)/(dashboard)/sidebar-dropdown.tsx
index 5babcbbd404..ed068d32eee 100644
--- a/apps/admin/app/(all)/(dashboard)/sidebar-dropdown.tsx
+++ b/apps/admin/app/(all)/(dashboard)/sidebar-dropdown.tsx
@@ -71,14 +71,14 @@ export const AdminSidebarDropdown = observer(function AdminSidebarDropdown() {
useEffect(() => {
if (csrfToken === undefined)
- authService.requestCSRFToken().then((data) => data?.csrf_token && setCsrfToken(data.csrf_token));
+ void authService.requestCSRFToken().then((data) => data?.csrf_token && setCsrfToken(data.csrf_token));
}, [csrfToken]);
return (
-
+
@@ -88,8 +88,8 @@ export const AdminSidebarDropdown = observer(function AdminSidebarDropdown() {
"cursor-default": !isSidebarCollapsed,
})}
>
-
-
+
+
{isSidebarCollapsed && (
@@ -109,7 +109,7 @@ export const AdminSidebarDropdown = observer(function AdminSidebarDropdown() {
{!isSidebarCollapsed && (
-
Instance admin
+ Instance admin
)}
@@ -123,7 +123,7 @@ export const AdminSidebarDropdown = observer(function AdminSidebarDropdown() {
src={getFileURL(currentUser.avatar_url)}
size={24}
shape="square"
- className="!text-14"
+ className="!text-body-sm-medium"
/>
diff --git a/apps/admin/app/(all)/(dashboard)/sidebar-help-section.tsx b/apps/admin/app/(all)/(dashboard)/sidebar-help-section.tsx
index 3d8d0b77a0a..146770c70dc 100644
--- a/apps/admin/app/(all)/(dashboard)/sidebar-help-section.tsx
+++ b/apps/admin/app/(all)/(dashboard)/sidebar-help-section.tsx
@@ -9,11 +9,9 @@ import { DiscordIcon, GithubIcon, PageIcon } from "@plane/propel/icons";
import { Tooltip } from "@plane/propel/tooltip";
import { cn } from "@plane/utils";
// hooks
-import { useTheme } from "@/hooks/store";
+import { useInstance, useTheme } from "@/hooks/store";
// assets
-import packageJson from "package.json";
-
const helpOptions = [
{
name: "Documentation",
@@ -36,6 +34,7 @@ export const AdminSidebarHelpSection = observer(function AdminSidebarHelpSection
// states
const [isNeedHelpOpen, setIsNeedHelpOpen] = useState(false);
// store
+ const { instance } = useInstance();
const { isSidebarCollapsed, toggleSidebar } = useTheme();
// refs
const helpOptionsRef = useRef
(null);
@@ -55,9 +54,9 @@ export const AdminSidebarHelpSection = observer(function AdminSidebarHelpSection
-
+
{!isSidebarCollapsed && "Redirect to Plane"}
@@ -69,7 +68,7 @@ export const AdminSidebarHelpSection = observer(function AdminSidebarHelpSection
}`}
onClick={() => setIsNeedHelpOpen((prev) => !prev)}
>
-
+
@@ -80,7 +79,7 @@ export const AdminSidebarHelpSection = observer(function AdminSidebarHelpSection
}`}
onClick={() => toggleSidebar(!isSidebarCollapsed)}
>
-
+
@@ -108,7 +107,7 @@ export const AdminSidebarHelpSection = observer(function AdminSidebarHelpSection
@@ -129,7 +128,7 @@ export const AdminSidebarHelpSection = observer(function AdminSidebarHelpSection
);
})}
-
Version: v{packageJson.version}
+
Version: v{instance?.current_version}
diff --git a/apps/admin/app/(all)/(dashboard)/sidebar-menu.tsx b/apps/admin/app/(all)/(dashboard)/sidebar-menu.tsx
index e7371fc69e1..3a81db3cc20 100644
--- a/apps/admin/app/(all)/(dashboard)/sidebar-menu.tsx
+++ b/apps/admin/app/(all)/(dashboard)/sidebar-menu.tsx
@@ -1,58 +1,20 @@
import { observer } from "mobx-react";
import Link from "next/link";
import { usePathname } from "next/navigation";
-import { Image, BrainCog, Cog, Lock, Mail } from "lucide-react";
// plane internal packages
-import { WorkspaceIcon } from "@plane/propel/icons";
import { Tooltip } from "@plane/propel/tooltip";
import { cn } from "@plane/utils";
// hooks
import { useTheme } from "@/hooks/store";
-
-const INSTANCE_ADMIN_LINKS = [
- {
- Icon: Cog,
- name: "General",
- description: "Identify your instances and get key details.",
- href: `/general/`,
- },
- {
- Icon: WorkspaceIcon,
- name: "Workspaces",
- description: "Manage all workspaces on this instance.",
- href: `/workspace/`,
- },
- {
- Icon: Mail,
- name: "Email",
- description: "Configure your SMTP controls.",
- href: `/email/`,
- },
- {
- Icon: Lock,
- name: "Authentication",
- description: "Configure authentication modes.",
- href: `/authentication/`,
- },
- {
- Icon: BrainCog,
- name: "Artificial intelligence",
- description: "Configure your OpenAI creds.",
- href: `/ai/`,
- },
- {
- Icon: Image,
- name: "Images in Plane",
- description: "Allow third-party image libraries.",
- href: `/image/`,
- },
-];
+import { useSidebarMenu } from "@/hooks/use-sidebar-menu";
export const AdminSidebarMenu = observer(function AdminSidebarMenu() {
- // store hooks
- const { isSidebarCollapsed, toggleSidebar } = useTheme();
// router
const pathName = usePathname();
+ // store hooks
+ const { isSidebarCollapsed, toggleSidebar } = useTheme();
+ // derived values
+ const sidebarMenu = useSidebarMenu();
const handleItemClick = () => {
if (window.innerWidth < 768) {
@@ -62,40 +24,27 @@ export const AdminSidebarMenu = observer(function AdminSidebarMenu() {
return (
- {INSTANCE_ADMIN_LINKS.map((item, index) => {
- const isActive = item.href === pathName || pathName.includes(item.href);
+ {sidebarMenu.map((item, index) => {
+ const isActive = item.href === pathName || pathName?.includes(item.href);
return (
{
}
{!isSidebarCollapsed && (
-
- {item.name}
-
-
- {item.description}
-
+
{item.name}
+
{item.description}
)}
diff --git a/apps/admin/app/(all)/(dashboard)/workspace/create/page.tsx b/apps/admin/app/(all)/(dashboard)/workspace/create/page.tsx
index 3fd441c2686..30950d87832 100644
--- a/apps/admin/app/(all)/(dashboard)/workspace/create/page.tsx
+++ b/apps/admin/app/(all)/(dashboard)/workspace/create/page.tsx
@@ -1,21 +1,21 @@
import { observer } from "mobx-react";
// components
+import { PageWrapper } from "@/components/common/page-wrapper";
+// types
import type { Route } from "./+types/page";
+// local
import { WorkspaceCreateForm } from "./form";
const WorkspaceCreatePage = observer(function WorkspaceCreatePage(_props: Route.ComponentProps) {
return (
-
-
-
Create a new workspace on this instance.
-
- You will need to invite users from Workspace Settings after you create this workspace.
-
-
-
-
-
-
+
+
+
);
});
diff --git a/apps/admin/app/(all)/(dashboard)/workspace/page.tsx b/apps/admin/app/(all)/(dashboard)/workspace/page.tsx
index c3222f1fb90..add3da10a3a 100644
--- a/apps/admin/app/(all)/(dashboard)/workspace/page.tsx
+++ b/apps/admin/app/(all)/(dashboard)/workspace/page.tsx
@@ -8,12 +8,13 @@ import { Button, getButtonStyling } from "@plane/propel/button";
import { setPromiseToast } from "@plane/propel/toast";
import type { TInstanceConfigurationKeys } from "@plane/types";
import { Loader, ToggleSwitch } from "@plane/ui";
-
import { cn } from "@plane/utils";
// components
+import { PageWrapper } from "@/components/common/page-wrapper";
import { WorkspaceListItem } from "@/components/workspace/list-item";
// hooks
import { useInstance, useWorkspace } from "@/hooks/store";
+// types
import type { Route } from "./+types/page";
const WorkspaceManagementPage = observer(function WorkspaceManagementPage(_props: Route.ComponentProps) {
@@ -68,99 +69,95 @@ const WorkspaceManagementPage = observer(function WorkspaceManagementPage(_props
};
return (
-
-
-
-
Workspaces on this instance
-
See all workspaces and control who can create them.
-
-
-
-
- {formattedConfig ? (
-
-
-
-
Prevent anyone else from creating a workspace.
-
- Toggling this on will let only you create workspaces. You will have to invite users to new
- workspaces.
-
+
+
+ {formattedConfig ? (
+
+
+
+
Prevent anyone else from creating a workspace.
+
+ Toggling this on will let only you create workspaces. You will have to invite users to new workspaces.
-
-
- {
- if (Boolean(parseInt(disableWorkspaceCreation)) === true) {
- updateConfig("DISABLE_WORKSPACE_CREATION", "0");
- } else {
- updateConfig("DISABLE_WORKSPACE_CREATION", "1");
- }
- }}
- size="sm"
- disabled={isSubmitting}
- />
-
+
+
+
+ {
+ if (Boolean(parseInt(disableWorkspaceCreation)) === true) {
+ updateConfig("DISABLE_WORKSPACE_CREATION", "0");
+ } else {
+ updateConfig("DISABLE_WORKSPACE_CREATION", "1");
+ }
+ }}
+ size="sm"
+ disabled={isSubmitting}
+ />
- ) : (
-
-
-
- )}
- {workspaceLoader !== "init-loader" ? (
- <>
-
-
-
- All workspaces on this instance • {workspaceIds.length}
- {workspaceLoader && ["mutation", "pagination"].includes(workspaceLoader) && (
-
- )}
-
-
- You can't yet delete workspaces and you can only go to the workspace if you are an Admin or a
- Member.
-
+
+ ) : (
+
+
+
+ )}
+ {workspaceLoader !== "init-loader" ? (
+ <>
+
+
+
+ All workspaces on this instance • {workspaceIds.length}
+ {workspaceLoader && ["mutation", "pagination"].includes(workspaceLoader) && (
+
+ )}
-
-
- Create workspace
-
+
+ You can't yet delete workspaces and you can only go to the workspace if you are an Admin or a
+ Member.
-
- {workspaceIds.map((workspaceId) => (
-
- ))}
+
+
+ Create workspace
+
- {hasNextPage && (
-
-
-
- )}
- >
- ) : (
-
-
-
-
-
-
- )}
-
+
+
+ {workspaceIds.map((workspaceId) => (
+
+ ))}
+
+ {hasNextPage && (
+
+
+
+ )}
+ >
+ ) : (
+
+
+
+
+
+
+ )}
-
+
);
});
diff --git a/apps/admin/app/(all)/(home)/auth-banner.tsx b/apps/admin/app/(all)/(home)/auth-banner.tsx
index 61fc1947196..a2db1ad656c 100644
--- a/apps/admin/app/(all)/(home)/auth-banner.tsx
+++ b/apps/admin/app/(all)/(home)/auth-banner.tsx
@@ -20,7 +20,7 @@ export function AuthBanner(props: TAuthBanner) {
{bannerData?.message}
handleBannerData && handleBannerData(undefined)}
>
diff --git a/apps/admin/app/(all)/(home)/auth-helpers.tsx b/apps/admin/app/(all)/(home)/auth-helpers.tsx
index d13147362c7..18f793cd138 100644
--- a/apps/admin/app/(all)/(home)/auth-helpers.tsx
+++ b/apps/admin/app/(all)/(home)/auth-helpers.tsx
@@ -117,14 +117,14 @@ export const getBaseAuthenticationModes: (props: TGetBaseAuthenticationModeProps
name: "Unique codes",
description:
"Log in or sign up for Plane using codes sent via email. You need to have set up SMTP to use this method.",
- icon:
,
+ icon:
,
config:
,
},
{
key: "passwords-login",
name: "Passwords",
description: "Allow members to create accounts with passwords and use it with their email addresses to sign in.",
- icon:
,
+ icon:
,
config:
,
},
{
diff --git a/apps/admin/app/root.tsx b/apps/admin/app/root.tsx
index 6ac6531a37e..a2a5c216be8 100644
--- a/apps/admin/app/root.tsx
+++ b/apps/admin/app/root.tsx
@@ -1,15 +1,15 @@
import type { ReactNode } from "react";
-import * as Sentry from "@sentry/react-router";
import { Links, Meta, Outlet, Scripts } from "react-router";
import type { LinksFunction } from "react-router";
+import * as Sentry from "@sentry/react-router";
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 { LogoSpinner } from "@/components/common/logo-spinner";
import globalStyles from "@/styles/globals.css?url";
+import { AppProviders } from "@/providers";
import type { Route } from "./+types/root";
-import { AppProviders } from "./providers";
// fonts
import "@fontsource-variable/inter";
import interVariableWoff2 from "@fontsource-variable/inter/files/inter-latin-wght-normal.woff2?url";
diff --git a/apps/admin/ce/components/authentication/index.ts b/apps/admin/ce/components/authentication/index.ts
deleted file mode 100644
index d2aa7485574..00000000000
--- a/apps/admin/ce/components/authentication/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from "./authentication-modes";
diff --git a/apps/admin/ce/components/common/index.ts b/apps/admin/ce/components/common/index.ts
deleted file mode 100644
index c6a1da8b627..00000000000
--- a/apps/admin/ce/components/common/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from "./upgrade-button";
diff --git a/apps/admin/ce/components/common/upgrade-button.tsx b/apps/admin/ce/components/common/upgrade-button.tsx
deleted file mode 100644
index cf656bbd5e7..00000000000
--- a/apps/admin/ce/components/common/upgrade-button.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import React from "react";
-// icons
-import { SquareArrowOutUpRight } from "lucide-react";
-// plane internal packages
-import { getButtonStyling } from "@plane/propel/button";
-import { cn } from "@plane/utils";
-
-export function UpgradeButton() {
- return (
-
- Upgrade
-
-
- );
-}
diff --git a/apps/admin/core/components/authentication/authentication-method-card.tsx b/apps/admin/core/components/authentication/authentication-method-card.tsx
index df330aa71ca..b420d13fb0f 100644
--- a/apps/admin/core/components/authentication/authentication-method-card.tsx
+++ b/apps/admin/core/components/authentication/authentication-method-card.tsx
@@ -16,7 +16,7 @@ export function AuthenticationMethodCard(props: Props) {
return (
diff --git a/apps/admin/core/components/authentication/gitea-config.tsx b/apps/admin/core/components/authentication/gitea-config.tsx
index 7fadf1e1a3a..e17e23ba108 100644
--- a/apps/admin/core/components/authentication/gitea-config.tsx
+++ b/apps/admin/core/components/authentication/gitea-config.tsx
@@ -44,7 +44,7 @@ export const GiteaConfiguration = observer(function GiteaConfiguration(props: Pr
) : (
-
+
Configure
)}
diff --git a/apps/admin/core/components/authentication/github-config.tsx b/apps/admin/core/components/authentication/github-config.tsx
index 46a1a708c9f..b058ecf1bd4 100644
--- a/apps/admin/core/components/authentication/github-config.tsx
+++ b/apps/admin/core/components/authentication/github-config.tsx
@@ -1,4 +1,3 @@
-import React from "react";
import { observer } from "mobx-react";
import Link from "next/link";
// icons
@@ -43,7 +42,7 @@ export const GithubConfiguration = observer(function GithubConfiguration(props:
) : (
-
+
Configure
)}
diff --git a/apps/admin/core/components/authentication/gitlab-config.tsx b/apps/admin/core/components/authentication/gitlab-config.tsx
index b3069e6cc06..697f407d56b 100644
--- a/apps/admin/core/components/authentication/gitlab-config.tsx
+++ b/apps/admin/core/components/authentication/gitlab-config.tsx
@@ -42,7 +42,7 @@ export const GitlabConfiguration = observer(function GitlabConfiguration(props:
) : (
-
+
Configure
)}
diff --git a/apps/admin/core/components/authentication/google-config.tsx b/apps/admin/core/components/authentication/google-config.tsx
index 61d89e33255..a130a82471d 100644
--- a/apps/admin/core/components/authentication/google-config.tsx
+++ b/apps/admin/core/components/authentication/google-config.tsx
@@ -42,7 +42,7 @@ export const GoogleConfiguration = observer(function GoogleConfiguration(props:
) : (
-
+
Configure
)}
diff --git a/apps/admin/core/components/common/header/core.ts b/apps/admin/core/components/common/header/core.ts
new file mode 100644
index 00000000000..db77b83fa14
--- /dev/null
+++ b/apps/admin/core/components/common/header/core.ts
@@ -0,0 +1,13 @@
+export const CORE_HEADER_SEGMENT_LABELS: Record = {
+ general: "General",
+ ai: "Artificial Intelligence",
+ email: "Email",
+ authentication: "Authentication",
+ image: "Image",
+ google: "Google",
+ github: "GitHub",
+ gitlab: "GitLab",
+ gitea: "Gitea",
+ workspace: "Workspace",
+ create: "Create",
+};
diff --git a/apps/admin/core/components/common/header/extended.ts b/apps/admin/core/components/common/header/extended.ts
new file mode 100644
index 00000000000..152b0824985
--- /dev/null
+++ b/apps/admin/core/components/common/header/extended.ts
@@ -0,0 +1 @@
+export const EXTENDED_HEADER_SEGMENT_LABELS: Record = {};
diff --git a/apps/admin/app/(all)/(dashboard)/header.tsx b/apps/admin/core/components/common/header/index.tsx
similarity index 70%
rename from apps/admin/app/(all)/(dashboard)/header.tsx
rename to apps/admin/core/components/common/header/index.tsx
index e769d38602e..65d6f5703b2 100644
--- a/apps/admin/app/(all)/(dashboard)/header.tsx
+++ b/apps/admin/core/components/common/header/index.tsx
@@ -7,51 +7,30 @@ import { Breadcrumbs } from "@plane/ui";
import { BreadcrumbLink } from "@/components/common/breadcrumb-link";
// hooks
import { useTheme } from "@/hooks/store";
+// local imports
+import { CORE_HEADER_SEGMENT_LABELS } from "./core";
+import { EXTENDED_HEADER_SEGMENT_LABELS } from "./extended";
export const HamburgerToggle = observer(function HamburgerToggle() {
const { isSidebarCollapsed, toggleSidebar } = useTheme();
return (
- toggleSidebar(!isSidebarCollapsed)}
>
-
+
);
});
+const HEADER_SEGMENT_LABELS = {
+ ...CORE_HEADER_SEGMENT_LABELS,
+ ...EXTENDED_HEADER_SEGMENT_LABELS,
+};
+
export const AdminHeader = observer(function AdminHeader() {
const pathName = usePathname();
- const getHeaderTitle = (pathName: string) => {
- switch (pathName) {
- case "general":
- return "General";
- case "ai":
- return "Artificial Intelligence";
- case "email":
- return "Email";
- case "authentication":
- return "Authentication";
- case "image":
- return "Image";
- case "google":
- return "Google";
- case "github":
- return "GitHub";
- case "gitlab":
- return "GitLab";
- case "gitea":
- return "Gitea";
- case "workspace":
- return "Workspace";
- case "create":
- return "Create";
- default:
- return pathName.toUpperCase();
- }
- };
-
// Function to dynamically generate breadcrumb items based on pathname
const generateBreadcrumbItems = (pathname: string) => {
const pathSegments = pathname.split("/").slice(1); // removing the first empty string.
@@ -61,14 +40,14 @@ export const AdminHeader = observer(function AdminHeader() {
const breadcrumbItems = pathSegments.map((segment) => {
currentUrl += "/" + segment;
return {
- title: getHeaderTitle(segment),
+ title: HEADER_SEGMENT_LABELS[segment] ?? segment.toUpperCase(),
href: currentUrl,
};
});
return breadcrumbItems;
};
- const breadcrumbItems = generateBreadcrumbItems(pathName);
+ const breadcrumbItems = generateBreadcrumbItems(pathName || "");
return (
diff --git a/apps/admin/core/components/common/page-wrapper.tsx b/apps/admin/core/components/common/page-wrapper.tsx
new file mode 100644
index 00000000000..e2f35a48849
--- /dev/null
+++ b/apps/admin/core/components/common/page-wrapper.tsx
@@ -0,0 +1,44 @@
+import type { ReactNode } from "react";
+// plane imports
+import { cn } from "@plane/utils";
+
+type TPageWrapperProps = {
+ children: ReactNode;
+ header?: {
+ title: string;
+ description: string | ReactNode;
+ actions?: ReactNode;
+ };
+ customHeader?: ReactNode;
+ size?: "lg" | "md";
+};
+
+export const PageWrapper = (props: TPageWrapperProps) => {
+ const { children, header, customHeader, size = "md" } = props;
+
+ return (
+
+ {customHeader ? (
+
{customHeader}
+ ) : (
+ header && (
+
+
+
{header.title}
+
{header.description}
+
+ {header.actions &&
{header.actions}
}
+
+ )
+ )}
+
+ {children}
+
+
+ );
+};
diff --git a/apps/admin/core/components/instance/setup-form.tsx b/apps/admin/core/components/instance/setup-form.tsx
index 4e4e199504d..4d98885e554 100644
--- a/apps/admin/core/components/instance/setup-form.tsx
+++ b/apps/admin/core/components/instance/setup-form.tsx
@@ -54,13 +54,13 @@ const defaultFromData: TFormData = {
export function InstanceSetupForm() {
// search params
const searchParams = useSearchParams();
- const firstNameParam = searchParams.get("first_name") || undefined;
- const lastNameParam = searchParams.get("last_name") || undefined;
- const companyParam = searchParams.get("company") || undefined;
- const emailParam = searchParams.get("email") || undefined;
- const isTelemetryEnabledParam = (searchParams.get("is_telemetry_enabled") === "True" ? true : false) || true;
- const errorCode = searchParams.get("error_code") || undefined;
- const errorMessage = searchParams.get("error_message") || undefined;
+ const firstNameParam = searchParams?.get("first_name") || undefined;
+ const lastNameParam = searchParams?.get("last_name") || undefined;
+ const companyParam = searchParams?.get("company") || undefined;
+ const emailParam = searchParams?.get("email") || undefined;
+ const isTelemetryEnabledParam = (searchParams?.get("is_telemetry_enabled") === "True" ? true : false) || true;
+ const errorCode = searchParams?.get("error_code") || undefined;
+ const errorMessage = searchParams?.get("error_message") || undefined;
// state
const [showPassword, setShowPassword] = useState({
password: false,
@@ -238,7 +238,7 @@ export function InstanceSetupForm() {
name="password"
type={showPassword.password ? "text" : "password"}
inputSize="md"
- placeholder="New password..."
+ placeholder="New password"
value={formData.password}
onChange={(e) => handleFormChange("password", e.target.value)}
hasError={errorData.type && errorData.type === EErrorCodes.INVALID_PASSWORD ? true : false}
diff --git a/apps/admin/core/components/workspace/list-item.tsx b/apps/admin/core/components/workspace/list-item.tsx
index 2f55ba2a1b5..c64ea648de7 100644
--- a/apps/admin/core/components/workspace/list-item.tsx
+++ b/apps/admin/core/components/workspace/list-item.tsx
@@ -23,13 +23,13 @@ export const WorkspaceListItem = observer(function WorkspaceListItem({ workspace
key={workspaceId}
href={`${WEB_BASE_URL}/${encodeURIComponent(workspace.slug)}`}
target="_blank"
- className="group flex items-center justify-between p-4 gap-2.5 truncate border border-subtle/70 hover:border-subtle bg-layer-1 hover:bg-layer-1-hover rounded-md"
+ className="group flex items-center justify-between p-3 gap-2.5 truncate border border-subtle hover:border-subtle-1 bg-layer-1 hover:bg-layer-1-hover hover:shadow-raised-100 rounded-lg"
rel="noreferrer"
>
{workspace?.logo_url && workspace.logo_url !== "" ? (
@@ -75,7 +75,7 @@ export const WorkspaceListItem = observer(function WorkspaceListItem({ workspace
-
+
);
diff --git a/apps/admin/ce/components/authentication/authentication-modes.tsx b/apps/admin/core/hooks/oauth/core.tsx
similarity index 51%
rename from apps/admin/ce/components/authentication/authentication-modes.tsx
rename to apps/admin/core/hooks/oauth/core.tsx
index 9c2348c81d2..dc04b40ff70 100644
--- a/apps/admin/ce/components/authentication/authentication-modes.tsx
+++ b/apps/admin/core/hooks/oauth/core.tsx
@@ -1,72 +1,61 @@
-import { observer } from "mobx-react";
-import { useTheme } from "next-themes";
import { KeyRound, Mails } from "lucide-react";
// types
import type {
+ TCoreInstanceAuthenticationModeKeys,
TGetBaseAuthenticationModeProps,
- TInstanceAuthenticationMethodKeys,
TInstanceAuthenticationModes,
} from "@plane/types";
-import { resolveGeneralTheme } from "@plane/utils";
-// components
+// assets
import giteaLogo from "@/app/assets/logos/gitea-logo.svg?url";
import githubLightModeImage from "@/app/assets/logos/github-black.png?url";
import githubDarkModeImage from "@/app/assets/logos/github-white.png?url";
-import GitlabLogo from "@/app/assets/logos/gitlab-logo.svg?url";
-import GoogleLogo from "@/app/assets/logos/google-logo.svg?url";
-import OIDCLogo from "@/app/assets/logos/oidc-logo.svg?url";
-import SAMLLogo from "@/app/assets/logos/saml-logo.svg?url";
-import { AuthenticationMethodCard } from "@/components/authentication/authentication-method-card";
+import gitlabLogo from "@/app/assets/logos/gitlab-logo.svg?url";
+import googleLogo from "@/app/assets/logos/google-logo.svg?url";
+// components
import { EmailCodesConfiguration } from "@/components/authentication/email-config-switch";
import { GiteaConfiguration } from "@/components/authentication/gitea-config";
import { GithubConfiguration } from "@/components/authentication/github-config";
import { GitlabConfiguration } from "@/components/authentication/gitlab-config";
import { GoogleConfiguration } from "@/components/authentication/google-config";
import { PasswordLoginConfiguration } from "@/components/authentication/password-config-switch";
-// plane admin components
-import { UpgradeButton } from "@/plane-admin/components/common";
-// assets
-
-export type TAuthenticationModeProps = {
- disabled: boolean;
- updateConfig: (key: TInstanceAuthenticationMethodKeys, value: string) => void;
-};
// Authentication methods
-export const getAuthenticationModes: (props: TGetBaseAuthenticationModeProps) => TInstanceAuthenticationModes[] = ({
+export const getCoreAuthenticationModesMap: (
+ props: TGetBaseAuthenticationModeProps
+) => Record = ({
disabled,
updateConfig,
resolvedTheme,
-}) => [
- {
+}) => ({
+ "unique-codes": {
key: "unique-codes",
name: "Unique codes",
description:
"Log in or sign up for Plane using codes sent via email. You need to have set up SMTP to use this method.",
- icon: ,
+ icon: ,
config: ,
},
- {
+ "passwords-login": {
key: "passwords-login",
name: "Passwords",
description: "Allow members to create accounts with passwords and use it with their email addresses to sign in.",
- icon: ,
+ icon: ,
config: ,
},
- {
+ google: {
key: "google",
name: "Google",
description: "Allow members to log in or sign up for Plane with their Google accounts.",
- icon:
,
+ icon:
,
config: ,
},
- {
+ github: {
key: "github",
name: "GitHub",
description: "Allow members to log in or sign up for Plane with their GitHub accounts.",
icon: (
),
config: ,
},
- {
+ gitlab: {
key: "gitlab",
name: "GitLab",
description: "Allow members to log in or sign up to plane with their GitLab accounts.",
- icon:
,
+ icon:
,
config: ,
},
- {
+ gitea: {
key: "gitea",
name: "Gitea",
description: "Allow members to log in or sign up to plane with their Gitea accounts.",
icon:
,
config: ,
},
- {
- key: "oidc",
- name: "OIDC",
- description: "Authenticate your users via the OpenID Connect protocol.",
- icon:
,
- config: ,
- unavailable: true,
- },
- {
- key: "saml",
- name: "SAML",
- description: "Authenticate your users via the Security Assertion Markup Language protocol.",
- icon:
,
- config: ,
- unavailable: true,
- },
-];
-
-export const AuthenticationModes = observer(function AuthenticationModes(props: TAuthenticationModeProps) {
- const { disabled, updateConfig } = props;
- // next-themes
- const { resolvedTheme } = useTheme();
-
- return (
-
- {getAuthenticationModes({ disabled, updateConfig, resolvedTheme }).map((method) => (
-
- ))}
-
- );
});
diff --git a/apps/admin/core/hooks/oauth/index.ts b/apps/admin/core/hooks/oauth/index.ts
new file mode 100644
index 00000000000..2982814e5b0
--- /dev/null
+++ b/apps/admin/core/hooks/oauth/index.ts
@@ -0,0 +1,19 @@
+import type { TInstanceAuthenticationModes } from "@plane/types";
+import { getCoreAuthenticationModesMap } from "./core";
+import type { TGetAuthenticationModeProps } from "./types";
+
+export const useAuthenticationModes = (props: TGetAuthenticationModeProps): TInstanceAuthenticationModes[] => {
+ // derived values
+ const authenticationModes = getCoreAuthenticationModesMap(props);
+
+ const availableAuthenticationModes: TInstanceAuthenticationModes[] = [
+ authenticationModes["unique-codes"],
+ authenticationModes["passwords-login"],
+ authenticationModes["google"],
+ authenticationModes["github"],
+ authenticationModes["gitlab"],
+ authenticationModes["gitea"],
+ ];
+
+ return availableAuthenticationModes;
+};
diff --git a/apps/admin/core/hooks/oauth/types.ts b/apps/admin/core/hooks/oauth/types.ts
new file mode 100644
index 00000000000..cf265152ac5
--- /dev/null
+++ b/apps/admin/core/hooks/oauth/types.ts
@@ -0,0 +1,7 @@
+import type { TInstanceAuthenticationMethodKeys } from "@plane/types";
+
+export type TGetAuthenticationModeProps = {
+ disabled: boolean;
+ updateConfig: (key: TInstanceAuthenticationMethodKeys, value: string) => void;
+ resolvedTheme: string | undefined;
+};
diff --git a/apps/admin/core/hooks/store/use-instance.tsx b/apps/admin/core/hooks/store/use-instance.tsx
index 5917df3fa08..508e8fe1ecb 100644
--- a/apps/admin/core/hooks/store/use-instance.tsx
+++ b/apps/admin/core/hooks/store/use-instance.tsx
@@ -1,6 +1,6 @@
import { useContext } from "react";
// store
-import { StoreContext } from "@/app/(all)/store.provider";
+import { StoreContext } from "@/providers/store.provider";
import type { IInstanceStore } from "@/store/instance.store";
export const useInstance = (): IInstanceStore => {
diff --git a/apps/admin/core/hooks/store/use-theme.tsx b/apps/admin/core/hooks/store/use-theme.tsx
index d5a1e820e32..289ea6ae4ab 100644
--- a/apps/admin/core/hooks/store/use-theme.tsx
+++ b/apps/admin/core/hooks/store/use-theme.tsx
@@ -1,6 +1,6 @@
import { useContext } from "react";
// store
-import { StoreContext } from "@/app/(all)/store.provider";
+import { StoreContext } from "@/providers/store.provider";
import type { IThemeStore } from "@/store/theme.store";
export const useTheme = (): IThemeStore => {
diff --git a/apps/admin/core/hooks/store/use-user.tsx b/apps/admin/core/hooks/store/use-user.tsx
index 56b988eb80e..80cd046b49b 100644
--- a/apps/admin/core/hooks/store/use-user.tsx
+++ b/apps/admin/core/hooks/store/use-user.tsx
@@ -1,6 +1,6 @@
import { useContext } from "react";
// store
-import { StoreContext } from "@/app/(all)/store.provider";
+import { StoreContext } from "@/providers/store.provider";
import type { IUserStore } from "@/store/user.store";
export const useUser = (): IUserStore => {
diff --git a/apps/admin/core/hooks/store/use-workspace.tsx b/apps/admin/core/hooks/store/use-workspace.tsx
index c4578c91702..957a33ff3c9 100644
--- a/apps/admin/core/hooks/store/use-workspace.tsx
+++ b/apps/admin/core/hooks/store/use-workspace.tsx
@@ -1,6 +1,6 @@
import { useContext } from "react";
// store
-import { StoreContext } from "@/app/(all)/store.provider";
+import { StoreContext } from "@/providers/store.provider";
import type { IWorkspaceStore } from "@/store/workspace.store";
export const useWorkspace = (): IWorkspaceStore => {
diff --git a/apps/admin/core/hooks/use-sidebar-menu/core.ts b/apps/admin/core/hooks/use-sidebar-menu/core.ts
new file mode 100644
index 00000000000..36b143598d8
--- /dev/null
+++ b/apps/admin/core/hooks/use-sidebar-menu/core.ts
@@ -0,0 +1,46 @@
+import { Image, BrainCog, Cog, Lock, Mail } from "lucide-react";
+// plane imports
+import { WorkspaceIcon } from "@plane/propel/icons";
+// types
+import type { TSidebarMenuItem } from "./types";
+
+export type TCoreSidebarMenuKey = "general" | "email" | "workspace" | "authentication" | "ai" | "image";
+
+export const coreSidebarMenuLinks: Record = {
+ general: {
+ Icon: Cog,
+ name: "General",
+ description: "Identify your instances and get key details.",
+ href: `/general/`,
+ },
+ email: {
+ Icon: Mail,
+ name: "Email",
+ description: "Configure your SMTP controls.",
+ href: `/email/`,
+ },
+ workspace: {
+ Icon: WorkspaceIcon,
+ name: "Workspaces",
+ description: "Manage all workspaces on this instance.",
+ href: `/workspace/`,
+ },
+ authentication: {
+ Icon: Lock,
+ name: "Authentication",
+ description: "Configure authentication modes.",
+ href: `/authentication/`,
+ },
+ ai: {
+ Icon: BrainCog,
+ name: "Artificial intelligence",
+ description: "Configure your OpenAI creds.",
+ href: `/ai/`,
+ },
+ image: {
+ Icon: Image,
+ name: "Images in Plane",
+ description: "Allow third-party image libraries.",
+ href: `/image/`,
+ },
+};
diff --git a/apps/admin/core/hooks/use-sidebar-menu/index.ts b/apps/admin/core/hooks/use-sidebar-menu/index.ts
new file mode 100644
index 00000000000..0f9e717dc62
--- /dev/null
+++ b/apps/admin/core/hooks/use-sidebar-menu/index.ts
@@ -0,0 +1,14 @@
+// local imports
+import { coreSidebarMenuLinks } from "./core";
+import type { TSidebarMenuItem } from "./types";
+
+export function useSidebarMenu(): TSidebarMenuItem[] {
+ return [
+ coreSidebarMenuLinks.general,
+ coreSidebarMenuLinks.email,
+ coreSidebarMenuLinks.authentication,
+ coreSidebarMenuLinks.workspace,
+ coreSidebarMenuLinks.ai,
+ coreSidebarMenuLinks.image,
+ ];
+}
diff --git a/apps/admin/core/hooks/use-sidebar-menu/types.ts b/apps/admin/core/hooks/use-sidebar-menu/types.ts
new file mode 100644
index 00000000000..d7a49a50d76
--- /dev/null
+++ b/apps/admin/core/hooks/use-sidebar-menu/types.ts
@@ -0,0 +1,8 @@
+import type { LucideIcon } from "lucide-react";
+
+export type TSidebarMenuItem = {
+ Icon: LucideIcon | React.ComponentType<{ className?: string }>;
+ name: string;
+ description: string;
+ href: string;
+};
diff --git a/apps/admin/app/providers.tsx b/apps/admin/core/providers/core.tsx
similarity index 70%
rename from apps/admin/app/providers.tsx
rename to apps/admin/core/providers/core.tsx
index 0406cec0929..d06d8f3f4aa 100644
--- a/apps/admin/app/providers.tsx
+++ b/apps/admin/core/providers/core.tsx
@@ -1,10 +1,11 @@
import { ThemeProvider } from "next-themes";
import { SWRConfig } from "swr";
import { AppProgressBar } from "@/lib/b-progress";
-import { InstanceProvider } from "./(all)/instance.provider";
-import { StoreProvider } from "./(all)/store.provider";
-import { ToastWithTheme } from "./(all)/toast";
-import { UserProvider } from "./(all)/user.provider";
+// local imports
+import { ToastWithTheme } from "./toast";
+import { StoreProvider } from "./store.provider";
+import { InstanceProvider } from "./instance.provider";
+import { UserProvider } from "./user.provider";
const DEFAULT_SWR_CONFIG = {
refreshWhenHidden: false,
@@ -15,7 +16,7 @@ const DEFAULT_SWR_CONFIG = {
errorRetryCount: 3,
};
-export function AppProviders({ children }: { children: React.ReactNode }) {
+export function CoreProviders({ children }: { children: React.ReactNode }) {
return (
diff --git a/apps/admin/core/providers/extended.tsx b/apps/admin/core/providers/extended.tsx
new file mode 100644
index 00000000000..60f36cbe477
--- /dev/null
+++ b/apps/admin/core/providers/extended.tsx
@@ -0,0 +1,3 @@
+export function ExtendedProviders({ children }: { children: React.ReactNode }) {
+ return <>{children}>;
+}
diff --git a/apps/admin/core/providers/index.tsx b/apps/admin/core/providers/index.tsx
new file mode 100644
index 00000000000..c0447b5bce1
--- /dev/null
+++ b/apps/admin/core/providers/index.tsx
@@ -0,0 +1,10 @@
+import { CoreProviders } from "./core";
+import { ExtendedProviders } from "./extended";
+
+export function AppProviders({ children }: { children: React.ReactNode }) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/apps/admin/app/(all)/instance.provider.tsx b/apps/admin/core/providers/instance.provider.tsx
similarity index 100%
rename from apps/admin/app/(all)/instance.provider.tsx
rename to apps/admin/core/providers/instance.provider.tsx
diff --git a/apps/admin/app/(all)/store.provider.tsx b/apps/admin/core/providers/store.provider.tsx
similarity index 100%
rename from apps/admin/app/(all)/store.provider.tsx
rename to apps/admin/core/providers/store.provider.tsx
diff --git a/apps/admin/app/(all)/toast.tsx b/apps/admin/core/providers/toast.tsx
similarity index 100%
rename from apps/admin/app/(all)/toast.tsx
rename to apps/admin/core/providers/toast.tsx
diff --git a/apps/admin/app/(all)/user.provider.tsx b/apps/admin/core/providers/user.provider.tsx
similarity index 100%
rename from apps/admin/app/(all)/user.provider.tsx
rename to apps/admin/core/providers/user.provider.tsx
diff --git a/apps/admin/ee/components/authentication/authentication-modes.tsx b/apps/admin/ee/components/authentication/authentication-modes.tsx
deleted file mode 100644
index 4e3b05a5228..00000000000
--- a/apps/admin/ee/components/authentication/authentication-modes.tsx
+++ /dev/null
@@ -1 +0,0 @@
-export * from "ce/components/authentication/authentication-modes";
diff --git a/apps/admin/ee/components/authentication/index.ts b/apps/admin/ee/components/authentication/index.ts
deleted file mode 100644
index d2aa7485574..00000000000
--- a/apps/admin/ee/components/authentication/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from "./authentication-modes";
diff --git a/apps/admin/ee/components/common/index.ts b/apps/admin/ee/components/common/index.ts
deleted file mode 100644
index 60441ee25be..00000000000
--- a/apps/admin/ee/components/common/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from "ce/components/common";
diff --git a/apps/space/core/components/account/auth-forms/auth-root.tsx b/apps/space/core/components/account/auth-forms/auth-root.tsx
index 0308471eccb..e313cce893a 100644
--- a/apps/space/core/components/account/auth-forms/auth-root.tsx
+++ b/apps/space/core/components/account/auth-forms/auth-root.tsx
@@ -1,22 +1,15 @@
import { useEffect, useState } from "react";
import { observer } from "mobx-react";
import { useSearchParams } from "next/navigation";
-import { useTheme } from "next-themes";
// plane imports
-import { API_BASE_URL } from "@plane/constants";
import { SitesAuthService } from "@plane/services";
import type { IEmailCheckData } from "@plane/types";
import { OAuthOptions } from "@plane/ui";
-// assets
-import GiteaLogo from "@/app/assets/logos/gitea-logo.svg?url";
-import GithubLightLogo from "@/app/assets/logos/github-black.png?url";
-import GithubDarkLogo from "@/app/assets/logos/github-dark.svg?url";
-import GitlabLogo from "@/app/assets/logos/gitlab-logo.svg?url";
-import GoogleLogo from "@/app/assets/logos/google-logo.svg?url";
// helpers
import type { TAuthErrorInfo } from "@/helpers/authentication.helper";
import { EErrorAlertType, authErrorHandler, EAuthenticationErrorCodes } from "@/helpers/authentication.helper";
// hooks
+import { useOAuthConfig } from "@/hooks/oauth";
import { useInstance } from "@/hooks/store/use-instance";
// types
import { EAuthModes, EAuthSteps } from "@/types/auth";
@@ -36,7 +29,6 @@ export const AuthRoot = observer(function AuthRoot() {
const emailParam = searchParams.get("email") || undefined;
const error_code = searchParams.get("error_code") || undefined;
const nextPath = searchParams.get("next_path") || undefined;
- const next_path = searchParams.get("next_path");
// states
const [authMode, setAuthMode] = useState(EAuthModes.SIGN_UP);
const [authStep, setAuthStep] = useState(EAuthSteps.EMAIL);
@@ -44,7 +36,6 @@ export const AuthRoot = observer(function AuthRoot() {
const [errorInfo, setErrorInfo] = useState(undefined);
const [isPasswordAutoset, setIsPasswordAutoset] = useState(true);
// hooks
- const { resolvedTheme } = useTheme();
const { config } = useInstance();
useEffect(() => {
@@ -87,13 +78,8 @@ export const AuthRoot = observer(function AuthRoot() {
const isSMTPConfigured = config?.is_smtp_configured || false;
const isMagicLoginEnabled = config?.is_magic_login_enabled || false;
const isEmailPasswordEnabled = config?.is_email_password_enabled || false;
- const isOAuthEnabled =
- (config &&
- (config?.is_google_enabled ||
- config?.is_github_enabled ||
- config?.is_gitlab_enabled ||
- config?.is_gitea_enabled)) ||
- false;
+ const oAuthActionText = authMode === EAuthModes.SIGN_UP ? "Sign up" : "Sign in";
+ const { isOAuthEnabled, oAuthOptions } = useOAuthConfig(oAuthActionText);
// submit handler- email verification
const handleEmailVerification = async (data: IEmailCheckData) => {
@@ -153,54 +139,6 @@ export const AuthRoot = observer(function AuthRoot() {
});
};
- const content = authMode === EAuthModes.SIGN_UP ? "Sign up" : "Sign in";
-
- const OAuthConfig = [
- {
- id: "google",
- text: `${content} with Google`,
- icon:
,
- onClick: () => {
- window.location.assign(`${API_BASE_URL}/auth/google/${next_path ? `?next_path=${next_path}` : ``}`);
- },
- enabled: config?.is_google_enabled,
- },
- {
- id: "github",
- text: `${content} with GitHub`,
- icon: (
-
- ),
- onClick: () => {
- window.location.assign(`${API_BASE_URL}/auth/github/${next_path ? `?next_path=${next_path}` : ``}`);
- },
- enabled: config?.is_github_enabled,
- },
- {
- id: "gitlab",
- text: `${content} with GitLab`,
- icon:
,
- onClick: () => {
- window.location.assign(`${API_BASE_URL}/auth/gitlab/${next_path ? `?next_path=${next_path}` : ``}`);
- },
- enabled: config?.is_gitlab_enabled,
- },
- {
- id: "gitea",
- text: `${content} with Gitea`,
- icon:
,
- onClick: () => {
- window.location.assign(`${API_BASE_URL}/auth/gitea/${next_path ? `?next_path=${next_path}` : ``}`);
- },
- enabled: config?.is_gitea_enabled,
- },
- ];
-
return (
@@ -208,7 +146,7 @@ export const AuthRoot = observer(function AuthRoot() {
setErrorInfo(value)} />
)}
- {isOAuthEnabled && }
+ {isOAuthEnabled && }
{authStep === EAuthSteps.EMAIL && }
{authStep === EAuthSteps.UNIQUE_CODE && (
diff --git a/apps/space/core/hooks/oauth/core.tsx b/apps/space/core/hooks/oauth/core.tsx
new file mode 100644
index 00000000000..54fce85e056
--- /dev/null
+++ b/apps/space/core/hooks/oauth/core.tsx
@@ -0,0 +1,82 @@
+// plane imports
+import { useSearchParams } from "next/navigation";
+import { useTheme } from "next-themes";
+import { API_BASE_URL } from "@plane/constants";
+import type { TOAuthConfigs, TOAuthOption } from "@plane/types";
+// assets
+import giteaLogo from "@/app/assets/logos/gitea-logo.svg?url";
+import githubLightLogo from "@/app/assets/logos/github-black.png?url";
+import githubDarkLogo from "@/app/assets/logos/github-dark.svg?url";
+import gitlabLogo from "@/app/assets/logos/gitlab-logo.svg?url";
+import googleLogo from "@/app/assets/logos/google-logo.svg?url";
+// hooks
+import { useInstance } from "@/hooks/store/use-instance";
+
+export const useCoreOAuthConfig = (oauthActionText: string): TOAuthConfigs => {
+ //router
+ const searchParams = useSearchParams();
+ // query params
+ const next_path = searchParams.get("next_path");
+ // theme
+ const { resolvedTheme } = useTheme();
+ // store hooks
+ const { config } = useInstance();
+ // derived values
+ const isOAuthEnabled =
+ (config &&
+ (config?.is_google_enabled ||
+ config?.is_github_enabled ||
+ config?.is_gitlab_enabled ||
+ config?.is_gitea_enabled)) ||
+ false;
+ const oAuthOptions: TOAuthOption[] = [
+ {
+ id: "google",
+ text: `${oauthActionText} with Google`,
+ icon:
,
+ onClick: () => {
+ window.location.assign(`${API_BASE_URL}/auth/google/${next_path ? `?next_path=${next_path}` : ``}`);
+ },
+ enabled: config?.is_google_enabled,
+ },
+ {
+ id: "github",
+ text: `${oauthActionText} with GitHub`,
+ icon: (
+
+ ),
+ onClick: () => {
+ window.location.assign(`${API_BASE_URL}/auth/github/${next_path ? `?next_path=${next_path}` : ``}`);
+ },
+ enabled: config?.is_github_enabled,
+ },
+ {
+ id: "gitlab",
+ text: `${oauthActionText} with GitLab`,
+ icon:
,
+ onClick: () => {
+ window.location.assign(`${API_BASE_URL}/auth/gitlab/${next_path ? `?next_path=${next_path}` : ``}`);
+ },
+ enabled: config?.is_gitlab_enabled,
+ },
+ {
+ id: "gitea",
+ text: `${oauthActionText} with Gitea`,
+ icon:
,
+ onClick: () => {
+ window.location.assign(`${API_BASE_URL}/auth/gitea/${next_path ? `?next_path=${next_path}` : ``}`);
+ },
+ enabled: config?.is_gitea_enabled,
+ },
+ ];
+
+ return {
+ isOAuthEnabled,
+ oAuthOptions,
+ };
+};
diff --git a/apps/space/core/hooks/oauth/extended.tsx b/apps/space/core/hooks/oauth/extended.tsx
new file mode 100644
index 00000000000..d6793f9de68
--- /dev/null
+++ b/apps/space/core/hooks/oauth/extended.tsx
@@ -0,0 +1,7 @@
+// plane imports
+import type { TOAuthConfigs } from "@plane/types";
+
+export const useExtendedOAuthConfig = (_oauthActionText: string): TOAuthConfigs => ({
+ isOAuthEnabled: false,
+ oAuthOptions: [],
+});
diff --git a/apps/space/core/hooks/oauth/index.ts b/apps/space/core/hooks/oauth/index.ts
new file mode 100644
index 00000000000..2b156487373
--- /dev/null
+++ b/apps/space/core/hooks/oauth/index.ts
@@ -0,0 +1,14 @@
+// plane imports
+import type { TOAuthConfigs } from "@plane/types";
+// local imports
+import { useCoreOAuthConfig } from "./core";
+import { useExtendedOAuthConfig } from "./extended";
+
+export const useOAuthConfig = (oauthActionText: string = "Continue"): TOAuthConfigs => {
+ const coreOAuthConfig = useCoreOAuthConfig(oauthActionText);
+ const extendedOAuthConfig = useExtendedOAuthConfig(oauthActionText);
+ return {
+ isOAuthEnabled: coreOAuthConfig.isOAuthEnabled || extendedOAuthConfig.isOAuthEnabled,
+ oAuthOptions: [...coreOAuthConfig.oAuthOptions, ...extendedOAuthConfig.oAuthOptions],
+ };
+};
diff --git a/apps/web/app/routes.ts b/apps/web/app/routes.ts
index 25899378395..e862bd15f44 100644
--- a/apps/web/app/routes.ts
+++ b/apps/web/app/routes.ts
@@ -2,75 +2,7 @@ import { route } from "@react-router/dev/routes";
import type { RouteConfigEntry } from "@react-router/dev/routes";
import { coreRoutes } from "./routes/core";
import { extendedRoutes } from "./routes/extended";
-
-/**
- * Merges two route configurations intelligently.
- * - Deep merges children when the same layout file exists in both arrays
- * - Deduplicates routes by file property, preferring extended over core
- * - Maintains order: core routes first, then extended routes at each level
- */
-function mergeRoutes(core: RouteConfigEntry[], extended: RouteConfigEntry[]): RouteConfigEntry[] {
- // Step 1: Create a Map to track routes by file path
- const routeMap = new Map();
-
- // Step 2: Process core routes first
- for (const coreRoute of core) {
- const fileKey = coreRoute.file;
- routeMap.set(fileKey, coreRoute);
- }
-
- // Step 3: Process extended routes
- for (const extendedRoute of extended) {
- const fileKey = extendedRoute.file;
-
- if (routeMap.has(fileKey)) {
- // Route exists in both - need to merge
- const coreRoute = routeMap.get(fileKey)!;
-
- // Check if both have children (layouts that need deep merging)
- if (coreRoute.children && extendedRoute.children) {
- // Deep merge: recursively merge children
- const mergedChildren = mergeRoutes(
- Array.isArray(coreRoute.children) ? coreRoute.children : [],
- Array.isArray(extendedRoute.children) ? extendedRoute.children : []
- );
- routeMap.set(fileKey, {
- ...extendedRoute,
- children: mergedChildren,
- });
- } else {
- // No children or only one has children - prefer extended
- routeMap.set(fileKey, extendedRoute);
- }
- } else {
- // Route only exists in extended
- routeMap.set(fileKey, extendedRoute);
- }
- }
-
- // Step 4: Build final array maintaining order (core first, then extended-only)
- const result: RouteConfigEntry[] = [];
-
- // Add all core routes (now merged or original)
- for (const coreRoute of core) {
- const fileKey = coreRoute.file;
- if (routeMap.has(fileKey)) {
- result.push(routeMap.get(fileKey)!);
- routeMap.delete(fileKey); // Remove so we don't add it again
- }
- }
-
- // Add remaining extended-only routes
- for (const extendedRoute of extended) {
- const fileKey = extendedRoute.file;
- if (routeMap.has(fileKey)) {
- result.push(routeMap.get(fileKey)!);
- routeMap.delete(fileKey);
- }
- }
-
- return result;
-}
+import { mergeRoutes } from "./routes/helper";
/**
* Main Routes Configuration
diff --git a/apps/web/app/routes/helper.ts b/apps/web/app/routes/helper.ts
new file mode 100644
index 00000000000..5db1c8855a9
--- /dev/null
+++ b/apps/web/app/routes/helper.ts
@@ -0,0 +1,70 @@
+import type { RouteConfigEntry } from "@react-router/dev/routes";
+
+/**
+ * Merges two route configurations intelligently.
+ * - Deep merges children when the same layout file exists in both arrays
+ * - Deduplicates routes by file property, preferring extended over core
+ * - Maintains order: core routes first, then extended routes at each level
+ */
+export function mergeRoutes(core: RouteConfigEntry[], extended: RouteConfigEntry[]): RouteConfigEntry[] {
+ // Step 1: Create a Map to track routes by file path
+ const routeMap = new Map();
+
+ // Step 2: Process core routes first
+ for (const coreRoute of core) {
+ const fileKey = coreRoute.file;
+ routeMap.set(fileKey, coreRoute);
+ }
+
+ // Step 3: Process extended routes
+ for (const extendedRoute of extended) {
+ const fileKey = extendedRoute.file;
+
+ if (routeMap.has(fileKey)) {
+ // Route exists in both - need to merge
+ const coreRoute = routeMap.get(fileKey)!;
+
+ // Check if both have children (layouts that need deep merging)
+ if (coreRoute.children && extendedRoute.children) {
+ // Deep merge: recursively merge children
+ const mergedChildren = mergeRoutes(
+ Array.isArray(coreRoute.children) ? coreRoute.children : [],
+ Array.isArray(extendedRoute.children) ? extendedRoute.children : []
+ );
+ routeMap.set(fileKey, {
+ ...extendedRoute,
+ children: mergedChildren,
+ });
+ } else {
+ // No children or only one has children - prefer extended
+ routeMap.set(fileKey, extendedRoute);
+ }
+ } else {
+ // Route only exists in extended
+ routeMap.set(fileKey, extendedRoute);
+ }
+ }
+
+ // Step 4: Build final array maintaining order (core first, then extended-only)
+ const result: RouteConfigEntry[] = [];
+
+ // Add all core routes (now merged or original)
+ for (const coreRoute of core) {
+ const fileKey = coreRoute.file;
+ if (routeMap.has(fileKey)) {
+ result.push(routeMap.get(fileKey)!);
+ routeMap.delete(fileKey); // Remove so we don't add it again
+ }
+ }
+
+ // Add remaining extended-only routes
+ for (const extendedRoute of extended) {
+ const fileKey = extendedRoute.file;
+ if (routeMap.has(fileKey)) {
+ result.push(routeMap.get(fileKey)!);
+ routeMap.delete(fileKey);
+ }
+ }
+
+ return result;
+}
diff --git a/apps/web/ce/helpers/oauth-config.tsx b/apps/web/ce/helpers/oauth-config.tsx
deleted file mode 100644
index 1985fbf73ea..00000000000
--- a/apps/web/ce/helpers/oauth-config.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-// plane imports
-import { API_BASE_URL } from "@plane/constants";
-import type { TOAuthOption } from "@plane/ui";
-// assets
-import GithubLightLogo from "@/app/assets/logos/github-black.png?url";
-import GithubDarkLogo from "@/app/assets/logos/github-dark.svg?url";
-import GitlabLogo from "@/app/assets/logos/gitlab-logo.svg?url";
-import GiteaLogo from "@/app/assets/logos/gitea-logo.svg?url";
-import GoogleLogo from "@/app/assets/logos/google-logo.svg?url";
-import type { IInstanceConfig } from "@plane/types";
-
-export type OAuthConfigParams = {
- OauthButtonContent: "Sign up" | "Sign in";
- next_path: string | null;
- config: IInstanceConfig | undefined;
- resolvedTheme: string | undefined;
-};
-
-export const isOAuthEnabled = (config: IInstanceConfig | undefined) =>
- (config &&
- (config?.is_google_enabled ||
- config?.is_github_enabled ||
- config?.is_gitlab_enabled ||
- config?.is_gitea_enabled)) ||
- false;
-
-export function OAUTH_CONFIG({
- OauthButtonContent,
- next_path,
- config,
- resolvedTheme,
-}: OAuthConfigParams): TOAuthOption[] {
- return [
- {
- id: "google",
- text: `${OauthButtonContent} with Google`,
- icon:
,
- onClick: () => {
- window.location.assign(`${API_BASE_URL}/auth/google/${next_path ? `?next_path=${next_path}` : ``}`);
- },
- enabled: config?.is_google_enabled || false,
- },
- {
- id: "github",
- text: `${OauthButtonContent} with GitHub`,
- icon: (
-
- ),
- onClick: () => {
- window.location.assign(`${API_BASE_URL}/auth/github/${next_path ? `?next_path=${next_path}` : ``}`);
- },
- enabled: config?.is_github_enabled || false,
- },
- {
- id: "gitlab",
- text: `${OauthButtonContent} with GitLab`,
- icon:
,
- onClick: () => {
- window.location.assign(`${API_BASE_URL}/auth/gitlab/${next_path ? `?next_path=${next_path}` : ``}`);
- },
- enabled: config?.is_gitlab_enabled || false,
- },
- {
- id: "gitea",
- text: `${OauthButtonContent} with Gitea`,
- icon:
,
- onClick: () => {
- window.location.assign(`${API_BASE_URL}/auth/gitea/${next_path ? `?next_path=${next_path}` : ``}`);
- },
- enabled: config?.is_gitea_enabled || false,
- },
- ];
-}
diff --git a/apps/web/core/components/account/auth-forms/auth-banner.tsx b/apps/web/core/components/account/auth-forms/auth-banner.tsx
index d8268c576bf..6e7abe6bcbd 100644
--- a/apps/web/core/components/account/auth-forms/auth-banner.tsx
+++ b/apps/web/core/components/account/auth-forms/auth-banner.tsx
@@ -1,23 +1,21 @@
-import type { FC } from "react";
import { Info } from "lucide-react";
+// plane imports
import { useTranslation } from "@plane/i18n";
import { CloseIcon } from "@plane/propel/icons";
-// plane imports
// helpers
-import type { TAuthErrorInfo } from "@/helpers/authentication.helper";
+import type React from "react";
type TAuthBanner = {
- bannerData: TAuthErrorInfo | undefined;
- handleBannerData?: (bannerData: TAuthErrorInfo | undefined) => void;
+ message: React.ReactNode;
+ handleBannerData?: (bannerData: undefined) => void;
};
export function AuthBanner(props: TAuthBanner) {
- const { bannerData, handleBannerData } = props;
+ const { message, handleBannerData } = props;
// translation
const { t } = useTranslation();
- if (!bannerData) return <>>;
-
+ if (!message) return <>>;
return (
- {bannerData?.message}
+ {message}