From f77263d67f3e07c194d059a60ed6e8040b83dc14 Mon Sep 17 00:00:00 2001 From: chkn Date: Wed, 29 Apr 2026 17:08:45 +0100 Subject: [PATCH] fix: show error messages in god-mode instance setup form The setup form's local EErrorCodes enum used string values like "INSTANCE_NOT_CONFIGURED" but the backend sends numeric error codes (e.g. 5155). The switch statement never matched, silently swallowing all errors and leaving the user staring at the same form with no feedback. Aligns the setup form with the sign-in form's working pattern: authErrorHandler + AuthBanner. Also adds INSTANCE_NOT_CONFIGURED and PASSWORD_TOO_WEAK to EAdminAuthErrorCodes, which were missing. Co-Authored-By: Claude Sonnet 4.6 --- apps/admin/app/(all)/(home)/auth-helpers.tsx | 11 +++ apps/admin/components/instance/setup-form.tsx | 81 +++++-------------- packages/constants/src/auth/index.ts | 2 + 3 files changed, 32 insertions(+), 62 deletions(-) diff --git a/apps/admin/app/(all)/(home)/auth-helpers.tsx b/apps/admin/app/(all)/(home)/auth-helpers.tsx index ea18dc99599..692bc1de590 100644 --- a/apps/admin/app/(all)/(home)/auth-helpers.tsx +++ b/apps/admin/app/(all)/(home)/auth-helpers.tsx @@ -21,6 +21,15 @@ const errorCodeMessages: { [key in EAdminAuthErrorCodes]: { title: string; message: (email?: string) => React.ReactNode }; } = { // admin + [EAdminAuthErrorCodes.INSTANCE_NOT_CONFIGURED]: { + title: `Instance not configured`, + message: () => `This Plane instance is not configured. Please contact your administrator.`, + }, + [EAdminAuthErrorCodes.PASSWORD_TOO_WEAK]: { + title: `Password too weak`, + message: () => + `Your password is too weak. Please use a mix of uppercase, lowercase, numbers, and special characters.`, + }, [EAdminAuthErrorCodes.ADMIN_ALREADY_EXIST]: { title: `Admin already exists`, message: () => `Admin already exists. Please try again.`, @@ -77,6 +86,8 @@ const errorCodeMessages: { export const authErrorHandler = (errorCode: EAdminAuthErrorCodes, email?: string): TAdminAuthErrorInfo | undefined => { const bannerAlertErrorCodes = [ + EAdminAuthErrorCodes.INSTANCE_NOT_CONFIGURED, + EAdminAuthErrorCodes.PASSWORD_TOO_WEAK, EAdminAuthErrorCodes.ADMIN_ALREADY_EXIST, EAdminAuthErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME, EAdminAuthErrorCodes.INVALID_ADMIN_EMAIL, diff --git a/apps/admin/components/instance/setup-form.tsx b/apps/admin/components/instance/setup-form.tsx index 74e80db45b6..9dcd0cd94ed 100644 --- a/apps/admin/components/instance/setup-form.tsx +++ b/apps/admin/components/instance/setup-form.tsx @@ -9,34 +9,21 @@ import { useSearchParams } from "next/navigation"; // icons import { Eye, EyeOff } from "lucide-react"; // plane internal packages -import { API_BASE_URL, E_PASSWORD_STRENGTH } from "@plane/constants"; +import { API_BASE_URL, E_PASSWORD_STRENGTH, EAdminAuthErrorCodes } from "@plane/constants"; +import type { TAdminAuthErrorInfo } from "@plane/constants"; import { Button } from "@plane/propel/button"; import { AuthService } from "@plane/services"; import { Checkbox, Input, PasswordStrengthIndicator, Spinner } from "@plane/ui"; import { getPasswordStrength, validatePersonName, validateCompanyName } from "@plane/utils"; // components +import { AuthBanner } from "@/app/(all)/(home)/auth-banner"; +import { authErrorHandler } from "@/app/(all)/(home)/auth-helpers"; import { AuthHeader } from "@/app/(all)/(home)/auth-header"; -import { Banner } from "../common/banner"; import { FormHeader } from "./form-header"; // service initialization const authService = new AuthService(); -// error codes -enum EErrorCodes { - INSTANCE_NOT_CONFIGURED = "INSTANCE_NOT_CONFIGURED", - ADMIN_ALREADY_EXIST = "ADMIN_ALREADY_EXIST", - REQUIRED_EMAIL_PASSWORD_FIRST_NAME = "REQUIRED_EMAIL_PASSWORD_FIRST_NAME", - INVALID_EMAIL = "INVALID_EMAIL", - INVALID_PASSWORD = "INVALID_PASSWORD", - USER_ALREADY_EXISTS = "USER_ALREADY_EXISTS", -} - -type TError = { - type: EErrorCodes | undefined; - message: string | undefined; -}; - // form data type TFormData = { first_name: string; @@ -64,9 +51,8 @@ export function InstanceSetupForm() { 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 isTelemetryEnabledParam = searchParams?.get("is_telemetry_enabled") === "True"; const errorCode = searchParams?.get("error_code") || undefined; - const errorMessage = searchParams?.get("error_message") || undefined; // state const [showPassword, setShowPassword] = useState({ password: false, @@ -77,6 +63,7 @@ export function InstanceSetupForm() { const [isPasswordInputFocused, setIsPasswordInputFocused] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); const [isRetryPasswordInputFocused, setIsRetryPasswordInputFocused] = useState(false); + const [errorInfo, setErrorInfo] = useState(undefined); const handleShowPassword = (key: keyof typeof showPassword) => setShowPassword((prev) => ({ ...prev, [key]: !prev[key] })); @@ -97,38 +84,21 @@ export function InstanceSetupForm() { if (isTelemetryEnabledParam) setFormData((prev) => ({ ...prev, is_telemetry_enabled: isTelemetryEnabledParam })); }, [firstNameParam, lastNameParam, companyParam, emailParam, isTelemetryEnabledParam]); - // derived values - const errorData: TError = useMemo(() => { - if (errorCode && errorMessage) { - switch (errorCode) { - case EErrorCodes.INSTANCE_NOT_CONFIGURED: - return { type: EErrorCodes.INSTANCE_NOT_CONFIGURED, message: errorMessage }; - case EErrorCodes.ADMIN_ALREADY_EXIST: - return { type: EErrorCodes.ADMIN_ALREADY_EXIST, message: errorMessage }; - case EErrorCodes.REQUIRED_EMAIL_PASSWORD_FIRST_NAME: - return { type: EErrorCodes.REQUIRED_EMAIL_PASSWORD_FIRST_NAME, message: errorMessage }; - case EErrorCodes.INVALID_EMAIL: - return { type: EErrorCodes.INVALID_EMAIL, message: errorMessage }; - case EErrorCodes.INVALID_PASSWORD: - return { type: EErrorCodes.INVALID_PASSWORD, message: errorMessage }; - case EErrorCodes.USER_ALREADY_EXISTS: - return { type: EErrorCodes.USER_ALREADY_EXISTS, message: errorMessage }; - default: - return { type: undefined, message: undefined }; - } - } else return { type: undefined, message: undefined }; - }, [errorCode, errorMessage]); + useEffect(() => { + if (errorCode) { + const errorDetail = authErrorHandler(errorCode as EAdminAuthErrorCodes); + if (errorDetail) setErrorInfo(errorDetail); + } + }, [errorCode]); const isButtonDisabled = useMemo( () => - !isSubmitting && - formData.first_name && - formData.email && - formData.password && - getPasswordStrength(formData.password) === E_PASSWORD_STRENGTH.STRENGTH_VALID && - formData.password === formData.confirm_password - ? false - : true, + isSubmitting || + !formData.first_name || + !formData.email || + !formData.password || + getPasswordStrength(formData.password) !== E_PASSWORD_STRENGTH.STRENGTH_VALID || + formData.password !== formData.confirm_password, [formData.confirm_password, formData.email, formData.first_name, formData.password, isSubmitting] ); @@ -145,11 +115,7 @@ export function InstanceSetupForm() { heading="Setup your Plane Instance" subHeading="Post setup you will be able to manage this Plane instance." /> - {errorData.type && - errorData?.message && - ![EErrorCodes.INVALID_EMAIL, EErrorCodes.INVALID_PASSWORD].includes(errorData.type) && ( - - )} + {errorInfo && setErrorInfo(v)} />}
@@ -221,12 +186,8 @@ export function InstanceSetupForm() { placeholder="name@company.com" value={formData.email} onChange={(e) => handleFormChange("email", e.target.value)} - hasError={errorData.type && errorData.type === EErrorCodes.INVALID_EMAIL ? true : false} autoComplete="off" /> - {errorData.type && errorData.type === EErrorCodes.INVALID_EMAIL && errorData.message && ( -

{errorData.message}

- )}
@@ -265,7 +226,6 @@ export function InstanceSetupForm() { placeholder="New password" value={formData.password} onChange={(e) => handleFormChange("password", e.target.value)} - hasError={errorData.type && errorData.type === EErrorCodes.INVALID_PASSWORD ? true : false} onFocus={() => setIsPasswordInputFocused(true)} onBlur={() => setIsPasswordInputFocused(false)} autoComplete="new-password" @@ -290,9 +250,6 @@ export function InstanceSetupForm() { )}
- {errorData.type && errorData.type === EErrorCodes.INVALID_PASSWORD && errorData.message && ( -

{errorData.message}

- )} diff --git a/packages/constants/src/auth/index.ts b/packages/constants/src/auth/index.ts index 32a7d5eee64..531642602ed 100644 --- a/packages/constants/src/auth/index.ts +++ b/packages/constants/src/auth/index.ts @@ -85,6 +85,8 @@ export type TAuthErrorInfo = { export enum EAdminAuthErrorCodes { // Admin + INSTANCE_NOT_CONFIGURED = "5000", + PASSWORD_TOO_WEAK = "5021", ADMIN_ALREADY_EXIST = "5150", REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME = "5155", INVALID_ADMIN_EMAIL = "5160",