diff --git a/apps/admin/app/(all)/(dashboard)/ai/form.tsx b/apps/admin/app/(all)/(dashboard)/ai/form.tsx index 3cb96a454c2..69456d8b61d 100644 --- a/apps/admin/app/(all)/(dashboard)/ai/form.tsx +++ b/apps/admin/app/(all)/(dashboard)/ai/form.tsx @@ -114,13 +114,13 @@ export function InstanceAIForm(props: IInstanceAIForm) { -
+
-
- +
+
If you have a preferred AI models vendor, please get in{" "} diff --git a/apps/admin/app/(all)/(dashboard)/ai/page.tsx b/apps/admin/app/(all)/(dashboard)/ai/page.tsx index 04a440fcf49..bf290ef3619 100644 --- a/apps/admin/app/(all)/(dashboard)/ai/page.tsx +++ b/apps/admin/app/(all)/(dashboard)/ai/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"; -// components +// types import type { Route } from "./+types/page"; +// local import { InstanceAIForm } from "./form"; const InstanceAIPage = observer(function InstanceAIPage(_props: Route.ComponentProps) { @@ -14,30 +17,25 @@ const InstanceAIPage = observer(function InstanceAIPage(_props: Route.ComponentP useSWR("INSTANCE_CONFIGURATIONS", () => fetchInstanceConfigurations()); return ( - <> -
-
-
AI features for all your workspaces
-
- Configure your AI API credentials so Plane AI features are turned on for all your workspaces. + + {formattedConfig ? ( + + ) : ( + + +
+ +
-
-
- {formattedConfig ? ( - - ) : ( - - -
- - -
- -
- )} -
-
- + + + )} + ); }); diff --git a/apps/admin/app/(all)/(dashboard)/authentication/gitea/form.tsx b/apps/admin/app/(all)/(dashboard)/authentication/gitea/form.tsx index 7cad550f846..c2e637e279a 100644 --- a/apps/admin/app/(all)/(dashboard)/authentication/gitea/form.tsx +++ b/apps/admin/app/(all)/(dashboard)/authentication/gitea/form.tsx @@ -196,7 +196,7 @@ export function InstanceGiteaConfigForm(props: Props) { loading={isSubmitting} disabled={!isDirty} > - {isSubmitting ? "Saving..." : "Save changes"} + {isSubmitting ? "Saving" : "Save changes"} Go back @@ -205,7 +205,7 @@ export function InstanceGiteaConfigForm(props: Props) {
-
+
Plane-provided details for Gitea
{GITEA_SERVICE_FIELD.map((field) => ( diff --git a/apps/admin/app/(all)/(dashboard)/authentication/gitea/page.tsx b/apps/admin/app/(all)/(dashboard)/authentication/gitea/page.tsx index 6dd90a2d188..f0f7e233380 100644 --- a/apps/admin/app/(all)/(dashboard)/authentication/gitea/page.tsx +++ b/apps/admin/app/(all)/(dashboard)/authentication/gitea/page.tsx @@ -4,13 +4,16 @@ import useSWR from "swr"; // plane internal packages import { setPromiseToast } from "@plane/propel/toast"; import { Loader, ToggleSwitch } from "@plane/ui"; -// components +// assets import giteaLogo from "@/app/assets/logos/gitea-logo.svg?url"; +// components import { AuthenticationMethodCard } from "@/components/authentication/authentication-method-card"; +import { PageWrapper } from "@/components/common/page-wrapper"; // hooks import { useInstance } from "@/hooks/store"; -//local components +// types import type { Route } from "./+types/page"; +// local import { InstanceGiteaConfigForm } from "./form"; const InstanceGiteaAuthenticationPage = observer(function InstanceGiteaAuthenticationPage() { @@ -32,7 +35,7 @@ const InstanceGiteaAuthenticationPage = observer(function InstanceGiteaAuthentic const updateConfigPromise = updateInstanceConfigurations(payload); setPromiseToast(updateConfigPromise, { - loading: "Saving Configuration...", + loading: "Saving Configuration", success: { title: "Configuration saved", message: () => `Gitea authentication is now ${value === "1" ? "active" : "disabled"}.`, @@ -56,42 +59,39 @@ const InstanceGiteaAuthenticationPage = observer(function InstanceGiteaAuthentic const isGiteaEnabled = enableGiteaConfig === "1"; return ( - <> -
-
- } - config={ - { - updateConfig("IS_GITEA_ENABLED", isGiteaEnabled ? "0" : "1"); - }} - size="sm" - disabled={isSubmitting || !formattedConfig} - /> - } - disabled={isSubmitting || !formattedConfig} - withBorder={false} - /> -
-
- {formattedConfig ? ( - - ) : ( - - - - - - - - )} -
-
- + } + config={ + { + updateConfig("IS_GITEA_ENABLED", isGiteaEnabled ? "0" : "1"); + }} + size="sm" + disabled={isSubmitting || !formattedConfig} + /> + } + disabled={isSubmitting || !formattedConfig} + withBorder={false} + /> + } + > + {formattedConfig ? ( + + ) : ( + + + + + + + + )} + ); }); export const meta: Route.MetaFunction = () => [{ title: "Gitea Authentication - God Mode" }]; diff --git a/apps/admin/app/(all)/(dashboard)/authentication/github/form.tsx b/apps/admin/app/(all)/(dashboard)/authentication/github/form.tsx index 2fb556fbd1e..c1aef0f6ccf 100644 --- a/apps/admin/app/(all)/(dashboard)/authentication/github/form.tsx +++ b/apps/admin/app/(all)/(dashboard)/authentication/github/form.tsx @@ -217,7 +217,7 @@ export function InstanceGithubConfigForm(props: Props) { loading={isSubmitting} disabled={!isDirty} > - {isSubmitting ? "Saving..." : "Save changes"} + {isSubmitting ? "Saving" : "Save changes"} Go back @@ -238,7 +238,7 @@ export function InstanceGithubConfigForm(props: Props) { {/* web service details */}
-
+
Web
diff --git a/apps/admin/app/(all)/(dashboard)/authentication/github/page.tsx b/apps/admin/app/(all)/(dashboard)/authentication/github/page.tsx index 438bfc9f94b..1186332d37a 100644 --- a/apps/admin/app/(all)/(dashboard)/authentication/github/page.tsx +++ b/apps/admin/app/(all)/(dashboard)/authentication/github/page.tsx @@ -6,15 +6,17 @@ import useSWR from "swr"; import { setPromiseToast } from "@plane/propel/toast"; import { Loader, ToggleSwitch } from "@plane/ui"; import { resolveGeneralTheme } from "@plane/utils"; -// components +// assets import githubLightModeImage from "@/app/assets/logos/github-black.png?url"; import githubDarkModeImage from "@/app/assets/logos/github-white.png?url"; +// components import { AuthenticationMethodCard } from "@/components/authentication/authentication-method-card"; +import { PageWrapper } from "@/components/common/page-wrapper"; // hooks import { useInstance } from "@/hooks/store"; -// icons -// local components +// types import type { Route } from "./+types/page"; +// local import { InstanceGithubConfigForm } from "./form"; const InstanceGithubAuthenticationPage = observer(function InstanceGithubAuthenticationPage( @@ -41,7 +43,7 @@ const InstanceGithubAuthenticationPage = observer(function InstanceGithubAuthent const updateConfigPromise = updateInstanceConfigurations(payload); setPromiseToast(updateConfigPromise, { - loading: "Saving Configuration...", + loading: "Saving Configuration", success: { title: "Configuration saved", message: () => `GitHub authentication is now ${value === "1" ? "active" : "disabled"}.`, @@ -65,49 +67,46 @@ const InstanceGithubAuthenticationPage = observer(function InstanceGithubAuthent const isGithubEnabled = enableGithubConfig === "1"; return ( - <> -
-
- - } - config={ - { - updateConfig("IS_GITHUB_ENABLED", isGithubEnabled ? "0" : "1"); - }} - size="sm" - disabled={isSubmitting || !formattedConfig} - /> - } - disabled={isSubmitting || !formattedConfig} - withBorder={false} - /> -
-
- {formattedConfig ? ( - - ) : ( - - - - - - - - )} -
-
- + + } + config={ + { + updateConfig("IS_GITHUB_ENABLED", isGithubEnabled ? "0" : "1"); + }} + size="sm" + disabled={isSubmitting || !formattedConfig} + /> + } + disabled={isSubmitting || !formattedConfig} + withBorder={false} + /> + } + > + {formattedConfig ? ( + + ) : ( + + + + + + + + )} + ); }); diff --git a/apps/admin/app/(all)/(dashboard)/authentication/gitlab/form.tsx b/apps/admin/app/(all)/(dashboard)/authentication/gitlab/form.tsx index 8f310fc28b9..4511a70c914 100644 --- a/apps/admin/app/(all)/(dashboard)/authentication/gitlab/form.tsx +++ b/apps/admin/app/(all)/(dashboard)/authentication/gitlab/form.tsx @@ -200,7 +200,7 @@ export function InstanceGitlabConfigForm(props: Props) { loading={isSubmitting} disabled={!isDirty} > - {isSubmitting ? "Saving..." : "Save changes"} + {isSubmitting ? "Saving" : "Save changes"} Go back @@ -209,7 +209,7 @@ export function InstanceGitlabConfigForm(props: Props) {
-
+
Plane-provided details for GitLab
{GITLAB_SERVICE_FIELD.map((field) => ( diff --git a/apps/admin/app/(all)/(dashboard)/authentication/gitlab/page.tsx b/apps/admin/app/(all)/(dashboard)/authentication/gitlab/page.tsx index 4a09927346e..0f3fd76a65d 100644 --- a/apps/admin/app/(all)/(dashboard)/authentication/gitlab/page.tsx +++ b/apps/admin/app/(all)/(dashboard)/authentication/gitlab/page.tsx @@ -3,14 +3,16 @@ import { observer } from "mobx-react"; import useSWR from "swr"; import { setPromiseToast } from "@plane/propel/toast"; import { Loader, ToggleSwitch } from "@plane/ui"; -// components +// assets import GitlabLogo from "@/app/assets/logos/gitlab-logo.svg?url"; +// components import { AuthenticationMethodCard } from "@/components/authentication/authentication-method-card"; +import { PageWrapper } from "@/components/common/page-wrapper"; // hooks import { useInstance } from "@/hooks/store"; -// icons -// local components +// types import type { Route } from "./+types/page"; +// local import { InstanceGitlabConfigForm } from "./form"; const InstanceGitlabAuthenticationPage = observer(function InstanceGitlabAuthenticationPage( @@ -35,7 +37,7 @@ const InstanceGitlabAuthenticationPage = observer(function InstanceGitlabAuthent const updateConfigPromise = updateInstanceConfigurations(payload); setPromiseToast(updateConfigPromise, { - loading: "Saving Configuration...", + loading: "Saving Configuration", success: { title: "Configuration saved", message: () => `GitLab authentication is now ${value === "1" ? "active" : "disabled"}.`, @@ -56,46 +58,43 @@ const InstanceGitlabAuthenticationPage = observer(function InstanceGitlabAuthent }); }; return ( - <> -
-
- } - config={ - { - if (Boolean(parseInt(enableGitlabConfig)) === true) { - updateConfig("IS_GITLAB_ENABLED", "0"); - } else { - updateConfig("IS_GITLAB_ENABLED", "1"); - } - }} - size="sm" - disabled={isSubmitting || !formattedConfig} - /> - } - disabled={isSubmitting || !formattedConfig} - withBorder={false} - /> -
-
- {formattedConfig ? ( - - ) : ( - - - - - - - - )} -
-
- + } + config={ + { + if (Boolean(parseInt(enableGitlabConfig)) === true) { + updateConfig("IS_GITLAB_ENABLED", "0"); + } else { + updateConfig("IS_GITLAB_ENABLED", "1"); + } + }} + size="sm" + disabled={isSubmitting || !formattedConfig} + /> + } + disabled={isSubmitting || !formattedConfig} + withBorder={false} + /> + } + > + {formattedConfig ? ( + + ) : ( + + + + + + + + )} + ); }); diff --git a/apps/admin/app/(all)/(dashboard)/authentication/google/form.tsx b/apps/admin/app/(all)/(dashboard)/authentication/google/form.tsx index 34b97b2792f..e068a1d07b5 100644 --- a/apps/admin/app/(all)/(dashboard)/authentication/google/form.tsx +++ b/apps/admin/app/(all)/(dashboard)/authentication/google/form.tsx @@ -205,7 +205,7 @@ export function InstanceGoogleConfigForm(props: Props) { loading={isSubmitting} disabled={!isDirty} > - {isSubmitting ? "Saving..." : "Save changes"} + {isSubmitting ? "Saving" : "Save changes"} Go back @@ -226,7 +226,7 @@ export function InstanceGoogleConfigForm(props: Props) { {/* web service details */}
-
+
Web
diff --git a/apps/admin/app/(all)/(dashboard)/authentication/google/page.tsx b/apps/admin/app/(all)/(dashboard)/authentication/google/page.tsx index 8089fcd6409..bf2a18d5a86 100644 --- a/apps/admin/app/(all)/(dashboard)/authentication/google/page.tsx +++ b/apps/admin/app/(all)/(dashboard)/authentication/google/page.tsx @@ -3,14 +3,16 @@ import { observer } from "mobx-react"; import useSWR from "swr"; import { setPromiseToast } from "@plane/propel/toast"; import { Loader, ToggleSwitch } from "@plane/ui"; -// components +// assets import GoogleLogo from "@/app/assets/logos/google-logo.svg?url"; +// components import { AuthenticationMethodCard } from "@/components/authentication/authentication-method-card"; +import { PageWrapper } from "@/components/common/page-wrapper"; // hooks import { useInstance } from "@/hooks/store"; -// icons -// local components +// types import type { Route } from "./+types/page"; +// local import { InstanceGoogleConfigForm } from "./form"; const InstanceGoogleAuthenticationPage = observer(function InstanceGoogleAuthenticationPage( @@ -35,7 +37,7 @@ const InstanceGoogleAuthenticationPage = observer(function InstanceGoogleAuthent const updateConfigPromise = updateInstanceConfigurations(payload); setPromiseToast(updateConfigPromise, { - loading: "Saving Configuration...", + loading: "Saving Configuration", success: { title: "Configuration saved", message: () => `Google authentication is now ${value === "1" ? "active" : "disabled"}.`, @@ -56,47 +58,44 @@ const InstanceGoogleAuthenticationPage = observer(function InstanceGoogleAuthent }); }; return ( - <> -
-
- } - config={ - { - if (Boolean(parseInt(enableGoogleConfig)) === true) { - updateConfig("IS_GOOGLE_ENABLED", "0"); - } else { - updateConfig("IS_GOOGLE_ENABLED", "1"); - } - }} - size="sm" - disabled={isSubmitting || !formattedConfig} - /> - } - disabled={isSubmitting || !formattedConfig} - withBorder={false} - /> -
-
- {formattedConfig ? ( - - ) : ( - - - - - - - - )} -
-
- + icon={Google Logo} + config={ + { + if (Boolean(parseInt(enableGoogleConfig)) === true) { + updateConfig("IS_GOOGLE_ENABLED", "0"); + } else { + updateConfig("IS_GOOGLE_ENABLED", "1"); + } + }} + size="sm" + disabled={isSubmitting || !formattedConfig} + /> + } + disabled={isSubmitting || !formattedConfig} + withBorder={false} + /> + } + > + {formattedConfig ? ( + + ) : ( + + + + + + + + )} + ); }); diff --git a/apps/admin/app/(all)/(dashboard)/authentication/page.tsx b/apps/admin/app/(all)/(dashboard)/authentication/page.tsx index 2ea34a3cac7..10adc312974 100644 --- a/apps/admin/app/(all)/(dashboard)/authentication/page.tsx +++ b/apps/admin/app/(all)/(dashboard)/authentication/page.tsx @@ -1,27 +1,33 @@ import { useState } from "react"; import { observer } from "mobx-react"; +import { useTheme } from "next-themes"; import useSWR from "swr"; // plane internal packages import { setPromiseToast } from "@plane/propel/toast"; import type { TInstanceConfigurationKeys } from "@plane/types"; import { Loader, ToggleSwitch } from "@plane/ui"; -import { cn } from "@plane/utils"; +import { cn, resolveGeneralTheme } from "@plane/utils"; +// components +import { PageWrapper } from "@/components/common/page-wrapper"; // hooks +import { AuthenticationMethodCard } from "@/components/authentication/authentication-method-card"; +import { useAuthenticationModes } from "@/hooks/oauth"; import { useInstance } from "@/hooks/store"; -// plane admin components -import { AuthenticationModes } from "@/plane-admin/components/authentication"; +// types import type { Route } from "./+types/page"; const InstanceAuthenticationPage = observer(function InstanceAuthenticationPage(_props: Route.ComponentProps) { + // theme + const { resolvedTheme: resolvedThemeAdmin } = useTheme(); // store const { fetchInstanceConfigurations, formattedConfig, updateInstanceConfigurations } = useInstance(); - - useSWR("INSTANCE_CONFIGURATIONS", () => fetchInstanceConfigurations()); - // state const [isSubmitting, setIsSubmitting] = useState(false); // derived values const enableSignUpConfig = formattedConfig?.ENABLE_SIGNUP ?? ""; + const resolvedTheme = resolveGeneralTheme(resolvedThemeAdmin); + + useSWR("INSTANCE_CONFIGURATIONS", () => fetchInstanceConfigurations()); const updateConfig = async (key: TInstanceConfigurationKeys, value: string) => { setIsSubmitting(true); @@ -54,59 +60,65 @@ const InstanceAuthenticationPage = observer(function InstanceAuthenticationPage( }); }; + const authenticationModes = useAuthenticationModes({ disabled: isSubmitting, updateConfig, resolvedTheme }); return ( - <> -
-
-
Manage authentication modes for your instance
-
- Configure authentication modes for your team and restrict sign-ups to be invite only. -
-
-
- {formattedConfig ? ( -
-
-
-
-
Allow anyone to sign up even without an invite
-
- Toggling this off will only let users sign up when they are invited. -
-
-
-
-
- { - if (Boolean(parseInt(enableSignUpConfig)) === true) { - updateConfig("ENABLE_SIGNUP", "0"); - } else { - updateConfig("ENABLE_SIGNUP", "1"); - } - }} - size="sm" - disabled={isSubmitting} - /> -
+ + {formattedConfig ? ( +
+
+
+
+
Allow anyone to sign up even without an invite
+
+ Toggling this off will only let users sign up when they are invited.
-
Available authentication modes
-
- ) : ( - - - - - - - - )} +
+
+ { + if (Boolean(parseInt(enableSignUpConfig)) === true) { + updateConfig("ENABLE_SIGNUP", "0"); + } else { + updateConfig("ENABLE_SIGNUP", "1"); + } + }} + size="sm" + disabled={isSubmitting} + /> +
+
+
+
Available authentication modes
+ {authenticationModes.map((method) => ( + + ))}
-
- + ) : ( + + + + + + + + )} + ); }); diff --git a/apps/admin/app/(all)/(dashboard)/email/email-config-form.tsx b/apps/admin/app/(all)/(dashboard)/email/email-config-form.tsx index e8ae5ed8d64..5fdc66b91c0 100644 --- a/apps/admin/app/(all)/(dashboard)/email/email-config-form.tsx +++ b/apps/admin/app/(all)/(dashboard)/email/email-config-form.tsx @@ -209,7 +209,7 @@ export function InstanceEmailForm(props: IInstanceEmailForm) { loading={isSubmitting} disabled={!isValid || !isDirty} > - {isSubmitting ? "Saving..." : "Save changes"} + {isSubmitting ? "Saving" : "Save changes"} {sendEmailStep === ESendEmailSteps.SEND_EMAIL && ( )}
diff --git a/apps/admin/app/(all)/(dashboard)/general/form.tsx b/apps/admin/app/(all)/(dashboard)/general/form.tsx index 0b5619ea00d..ff7b00e0dcd 100644 --- a/apps/admin/app/(all)/(dashboard)/general/form.tsx +++ b/apps/admin/app/(all)/(dashboard)/general/form.tsx @@ -1,17 +1,17 @@ import { observer } from "mobx-react"; import { Controller, useForm } from "react-hook-form"; import { Telescope } from "lucide-react"; -// types +// plane imports import { Button } from "@plane/propel/button"; import { TOAST_TYPE, setToast } from "@plane/propel/toast"; import type { IInstance, IInstanceAdmin } from "@plane/types"; -// ui import { Input, ToggleSwitch } from "@plane/ui"; // components import { ControllerInput } from "@/components/common/controller-input"; +// hooks import { useInstance } from "@/hooks/store"; +// components import { IntercomConfig } from "./intercom"; -// hooks export interface IGeneralConfigurationForm { instance: IInstance; @@ -27,8 +27,8 @@ export const GeneralConfigurationForm = observer(function GeneralConfigurationFo const { handleSubmit, control, - watch, formState: { errors, isSubmitting }, + watch, } = useForm>({ defaultValues: { instance_name: instance?.instance_name, @@ -105,14 +105,14 @@ export const GeneralConfigurationForm = observer(function GeneralConfigurationFo
-
-
Chat + telemetry
+
+
Chat + telemetry
-
+
-
- +
+
@@ -144,8 +144,15 @@ export const GeneralConfigurationForm = observer(function GeneralConfigurationFo
-
diff --git a/apps/admin/app/(all)/(dashboard)/general/intercom.tsx b/apps/admin/app/(all)/(dashboard)/general/intercom.tsx index d5ec8d352e4..0704b7c47bc 100644 --- a/apps/admin/app/(all)/(dashboard)/general/intercom.tsx +++ b/apps/admin/app/(all)/(dashboard)/general/intercom.tsx @@ -44,16 +44,16 @@ export const IntercomConfig = observer(function IntercomConfig(props: TIntercomC }; const enableIntercomConfig = () => { - submitInstanceConfigurations({ IS_INTERCOM_ENABLED: isIntercomEnabled ? "0" : "1" }); + void submitInstanceConfigurations({ IS_INTERCOM_ENABLED: isIntercomEnabled ? "0" : "1" }); }; return ( <> -
+
-
- +
+
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
- +
{name}
@@ -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: Google Logo, + icon: Google Logo, config: , }, - { + github: { key: "github", name: "GitHub", description: "Allow members to log in or sign up for Plane with their GitHub accounts.", icon: ( GitHub Logo ), config: , }, - { + gitlab: { key: "gitlab", name: "GitLab", description: "Allow members to log in or sign up to plane with their GitLab accounts.", - icon: GitLab Logo, + icon: GitLab Logo, config: , }, - { + gitea: { key: "gitea", name: "Gitea", description: "Allow members to log in or sign up to plane with their Gitea accounts.", icon: Gitea Logo, config: , }, - { - key: "oidc", - name: "OIDC", - description: "Authenticate your users via the OpenID Connect protocol.", - icon: OIDC Logo, - config: , - unavailable: true, - }, - { - key: "saml", - name: "SAML", - description: "Authenticate your users via the Security Assertion Markup Language protocol.", - icon: SAML Logo, - 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: Google Logo, - 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: ( - GitHub Logo - ), - 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: GitLab Logo, - 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: Gitea Logo, - 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: Google Logo, + 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: ( + GitHub Logo + ), + 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: GitLab Logo, + 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: Gitea Logo, + 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: Google Logo, - 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: ( - GitHub Logo - ), - 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: GitLab Logo, - 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: Gitea Logo, - 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}