import { useCallback, useRef, useState } from "react"; import { observer } from "mobx-react"; import { useTheme } from "next-themes"; import useSWR from "swr"; // plane internal packages import { setPromiseToast, setToast, TOAST_TYPE } from "@plane/propel/toast"; import type { TInstanceConfigurationKeys, TInstanceAuthenticationModes } from "@plane/types"; import { Loader, ToggleSwitch } from "@plane/ui"; import { cn, resolveGeneralTheme } from "@plane/utils"; // components import { PageWrapper } from "@/components/common/page-wrapper"; import { AuthenticationMethodCard } from "@/components/authentication/authentication-method-card"; // helpers import { canDisableAuthMethod } from "@/helpers/authentication"; // hooks import { useAuthenticationModes } from "@/hooks/oauth"; import { useInstance } from "@/hooks/store"; // types import type { Route } from "./+types/page"; const InstanceAuthenticationPage = observer(function InstanceAuthenticationPage(_props: Route.ComponentProps) { // theme const { resolvedTheme: resolvedThemeAdmin } = useTheme(); const resolvedTheme = resolveGeneralTheme(resolvedThemeAdmin); // Ref to store authentication modes for validation (avoids circular dependency) const authenticationModesRef = useRef([]); // state const [isSubmitting, setIsSubmitting] = useState(false); // store hooks const { fetchInstanceConfigurations, formattedConfig, updateInstanceConfigurations } = useInstance(); // derived values const enableSignUpConfig = formattedConfig?.ENABLE_SIGNUP ?? ""; useSWR("INSTANCE_CONFIGURATIONS", () => fetchInstanceConfigurations()); // Create updateConfig with validation - uses authenticationModesRef for current modes const updateConfig = useCallback( (key: TInstanceConfigurationKeys, value: string): void => { // Check if trying to disable (value === "0") if (value === "0") { // Check if this key is an authentication method key const currentAuthModes = authenticationModesRef.current; const isAuthMethodKey = currentAuthModes.some((method) => method.enabledConfigKey === key); // Only validate if this is an authentication method key if (isAuthMethodKey) { const canDisable = canDisableAuthMethod(key, currentAuthModes, formattedConfig); if (!canDisable) { setToast({ type: TOAST_TYPE.ERROR, title: "Cannot disable authentication", message: "At least one authentication method must remain enabled. Please enable another method before disabling this one.", }); return; } } } // Proceed with the update setIsSubmitting(true); const payload = { [key]: value, }; const updateConfigPromise = updateInstanceConfigurations(payload); setPromiseToast(updateConfigPromise, { loading: "Saving configuration", success: { title: "Success", message: () => "Configuration saved successfully", }, error: { title: "Error", message: () => "Failed to save configuration", }, }); void updateConfigPromise .then(() => { setIsSubmitting(false); return undefined; }) .catch((err) => { console.error(err); setIsSubmitting(false); }); }, [formattedConfig, updateInstanceConfigurations] ); // Get authentication modes - this will use updateConfig which includes validation const authenticationModes = useAuthenticationModes({ disabled: isSubmitting, updateConfig, resolvedTheme, }); // Update ref with latest authentication modes authenticationModesRef.current = authenticationModes; return ( {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} />
Available authentication modes
{authenticationModes.map((method) => ( ))}
) : ( )}
); }); export const meta: Route.MetaFunction = () => [{ title: "Authentication Settings - Plane Web" }]; export default InstanceAuthenticationPage;