Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions apps/admin/app/(all)/(home)/auth-helpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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.`,
Expand Down Expand Up @@ -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,
Expand Down
81 changes: 19 additions & 62 deletions apps/admin/components/instance/setup-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -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<TAdminAuthErrorInfo | undefined>(undefined);

const handleShowPassword = (key: keyof typeof showPassword) =>
setShowPassword((prev) => ({ ...prev, [key]: !prev[key] }));
Expand All @@ -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]);
Comment on lines +87 to +92
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Clear stale banner state when error_code is absent or unrecognized.

Current logic only sets state on recognized codes; it does not reset previous errors when error_code is removed/changed. That can leave an outdated banner visible.

💡 Proposed fix
   useEffect(() => {
-    if (errorCode) {
-      const errorDetail = authErrorHandler(errorCode as EAdminAuthErrorCodes);
-      if (errorDetail) setErrorInfo(errorDetail);
-    }
+    setErrorInfo(errorCode ? authErrorHandler(errorCode as EAdminAuthErrorCodes) : undefined);
   }, [errorCode]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
if (errorCode) {
const errorDetail = authErrorHandler(errorCode as EAdminAuthErrorCodes);
if (errorDetail) setErrorInfo(errorDetail);
}
}, [errorCode]);
useEffect(() => {
setErrorInfo(errorCode ? authErrorHandler(errorCode as EAdminAuthErrorCodes) : undefined);
}, [errorCode]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/admin/components/instance/setup-form.tsx` around lines 87 - 92, The
useEffect watching errorCode only sets errorInfo when
authErrorHandler(errorCode) returns a detail, but it never clears stale state;
update the effect in setup-form.tsx so that inside the effect you call
authErrorHandler(errorCode as EAdminAuthErrorCodes) and if it returns a detail
setErrorInfo(detail), otherwise call setErrorInfo(undefined) (also handle the
case when errorCode is falsy by clearing via setErrorInfo(undefined)),
referencing the existing useEffect, errorCode, authErrorHandler, and
setErrorInfo symbols to locate and change the logic.


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]
);

Expand All @@ -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) && (
<Banner type="error" message={errorData?.message} />
)}
{errorInfo && <AuthBanner bannerData={errorInfo} handleBannerData={(v) => setErrorInfo(v)} />}
<form
className="space-y-4"
method="POST"
Expand Down Expand Up @@ -180,7 +146,6 @@ export function InstanceSetupForm() {
}
}}
autoComplete="off"
autoFocus
maxLength={50}
/>
</div>
Expand Down Expand Up @@ -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 && (
<p className="px-1 text-11 text-danger-primary">{errorData.message}</p>
)}
</div>

<div className="w-full space-y-1">
Expand Down Expand Up @@ -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"
Expand All @@ -290,9 +250,6 @@ export function InstanceSetupForm() {
</button>
)}
</div>
{errorData.type && errorData.type === EErrorCodes.INVALID_PASSWORD && errorData.message && (
<p className="px-1 text-11 text-danger-primary">{errorData.message}</p>
)}
<PasswordStrengthIndicator password={formData.password} isFocused={isPasswordInputFocused} />
</div>

Expand Down
2 changes: 2 additions & 0 deletions packages/constants/src/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down