chore: authentication workflow changes

This commit is contained in:
gurusainath 2024-04-30 17:04:49 +05:30
parent c480ea34de
commit 49fe79367a
18 changed files with 443 additions and 141 deletions

View File

@ -29,8 +29,8 @@ class SignInAuthEndpoint(View):
if instance is None or not instance.is_setup_done:
# Redirection params
params = {
"error_code": "REQUIRED_EMAIL_PASSWORD",
"error_message": "Both email and password are required",
"error_code": "INSTANCE_NOT_CONFIGURED",
"error_message": "Instance is not configured",
}
if next_path:
params["next_path"] = str(next_path)

View File

@ -26,7 +26,7 @@ from plane.authentication.utils.workspace_project_join import (
from plane.bgtasks.magic_link_code_task import magic_link
from plane.license.models import Instance
from plane.authentication.utils.host import base_host
from plane.db.models import User
from plane.db.models import User, Profile
class MagicGenerateEndpoint(APIView):
@ -123,11 +123,12 @@ class MagicSignInEndpoint(View):
request=request, key=f"magic_{email}", code=code
)
user = provider.authenticate()
profile = Profile.objects.get(user=user)
# Login the user and record his device info
user_login(request=request, user=user)
# Process workspace and project invitations
process_workspace_project_invitations(user=user)
if user.is_password_autoset:
if user.is_password_autoset and profile.is_onboarded:
path = "accounts/set-password"
else:
# Get the redirection path

View File

@ -22,7 +22,7 @@ from plane.authentication.utils.login import user_login
from plane.bgtasks.magic_link_code_task import magic_link
from plane.license.models import Instance
from plane.authentication.utils.host import base_host
from plane.db.models import User
from plane.db.models import User, Profile
class MagicGenerateSpaceEndpoint(APIView):

View File

@ -26,6 +26,6 @@ export interface IPasswordSignInData {
password: string;
}
export interface ICsrfTokenData {
export interface ICsrfTokenData {
csrf_token: string;
};
}

View File

@ -0,0 +1,27 @@
import { FC } from "react";
import { Info, X } from "lucide-react";
// helpers
import { TAuthErrorInfo } from "@/helpers/authentication.helper";
type TAuthBanner = {
bannerData: TAuthErrorInfo | undefined;
handleBannerData?: (bannerData: TAuthErrorInfo | undefined) => void;
};
export const AuthBanner: FC<TAuthBanner> = (props) => {
const { bannerData, handleBannerData } = props;
if (!bannerData) return <></>;
return (
<div className="relative inline-flex items-center p-3 rounded-md gap-3 border border-custom-primary-100/50 bg-custom-primary-100/10">
<Info className="w-5 h-5 flex-shrink-0 text-custom-primary-100" />
<div className="w-full text-sm font-medium text-custom-primary-100">{bannerData?.message}</div>
<div
className="relative ml-auto w-6 h-6 rounded-sm flex justify-center items-center transition-all cursor-pointer hover:bg-custom-primary-100/20 text-custom-primary-100/80"
onClick={() => handleBannerData && handleBannerData(undefined)}
>
<X className="w-4 h-4 flex-shrink-0" />
</div>
</div>
);
};

View File

@ -0,0 +1,99 @@
import { FC, useEffect, useState } from "react";
import { IWorkspaceMemberInvitation } from "@plane/types";
// components
import { WorkspaceLogo } from "@/components/workspace/logo";
// helpers
import { EAuthModes, EAuthSteps } from "@/helpers/authentication.helper";
// services
import { WorkspaceService } from "@/services/workspace.service";
type TAuthHeader = {
workspaceSlug: string | undefined;
invitationId: string | undefined;
invitationEmail: string | undefined;
authMode: EAuthModes;
currentAuthStep: EAuthSteps;
handleLoader: (isLoading: boolean) => void;
};
const Titles = {
[EAuthModes.SIGN_IN]: {
[EAuthSteps.EMAIL]: {
header: "Sign in to Plane",
subHeader: "Get back to your projects and make progress",
},
[EAuthSteps.PASSWORD]: {
header: "Sign in to Plane",
subHeader: "Get back to your projects and make progress",
},
[EAuthSteps.UNIQUE_CODE]: {
header: "Sign in to Plane",
subHeader: "Get back to your projects and make progress",
},
},
[EAuthModes.SIGN_UP]: {
[EAuthSteps.EMAIL]: {
header: "Create your account",
subHeader: "Start tracking your projects with Plane",
},
[EAuthSteps.PASSWORD]: {
header: "Create your account",
subHeader: "Progress, visualize, and measure work how it works best for you.",
},
[EAuthSteps.UNIQUE_CODE]: {
header: "Create your account",
subHeader: "Progress, visualize, and measure work how it works best for you.",
},
},
};
const workSpaceService = new WorkspaceService();
export const AuthHeader: FC<TAuthHeader> = (props) => {
const { workspaceSlug, invitationId, invitationEmail, authMode, currentAuthStep, handleLoader } = props;
// state
const [invitation, setInvitation] = useState<IWorkspaceMemberInvitation | undefined>(undefined);
const getHeaderSubHeader = (
step: EAuthSteps,
mode: EAuthModes,
invitation: IWorkspaceMemberInvitation | undefined,
email: string | undefined
) => {
if (invitation && email && invitation.email === email && invitation.workspace) {
const workspace = invitation.workspace;
return {
header: (
<>
Join <WorkspaceLogo logo={workspace?.logo} name={workspace?.name} classNames="w-8 h-9" /> {workspace.name}
</>
),
subHeader: `${
mode == EAuthModes.SIGN_UP ? "Create an account" : "Sign in"
} to start managing work with your team.`,
};
}
return Titles[mode][step];
};
useEffect(() => {
if (workspaceSlug && invitationId) {
handleLoader(true);
workSpaceService
.getWorkspaceInvitation(workspaceSlug, invitationId)
.then((res) => setInvitation(res))
.catch(() => setInvitation(undefined))
.finally(() => handleLoader(false));
} else setInvitation(undefined);
}, [workspaceSlug, invitationId, handleLoader]);
const { header, subHeader } = getHeaderSubHeader(currentAuthStep, authMode, invitation, invitationEmail);
return (
<div className="space-y-1 text-center">
<h3 className="text-3xl font-bold text-onboarding-text-100">{header}</h3>
<p className="font-medium text-onboarding-text-400">{subHeader}</p>
</div>
);
};

View File

@ -1,6 +1,5 @@
import React from "react";
import { FC, FormEvent, useMemo, useState } from "react";
import { observer } from "mobx-react-lite";
import { Controller, useForm } from "react-hook-form";
// icons
import { CircleAlert, XCircle } from "lucide-react";
// types
@ -10,82 +9,72 @@ import { Button, Input } from "@plane/ui";
// helpers
import { checkEmailValidity } from "@/helpers/string.helper";
type Props = {
onSubmit: (data: IEmailCheckData) => Promise<void>;
type TAuthEmailForm = {
defaultEmail: string;
onSubmit: (data: IEmailCheckData) => Promise<void>;
};
type TEmailFormValues = {
email: string;
};
export const AuthEmailForm: React.FC<Props> = observer((props) => {
export const AuthEmailForm: FC<TAuthEmailForm> = observer((props) => {
const { onSubmit, defaultEmail } = props;
// hooks
const {
control,
formState: { errors, isSubmitting, isValid },
handleSubmit,
} = useForm<TEmailFormValues>({
defaultValues: {
email: defaultEmail,
},
mode: "onChange",
reValidateMode: "onChange",
});
// states
const [isSubmitting, setIsSubmitting] = useState(false);
const [email, setEmail] = useState(defaultEmail);
const handleFormSubmit = async (data: TEmailFormValues) => {
const emailError = useMemo(
() => (email && !checkEmailValidity(email) ? { email: "Email is invalid" } : undefined),
[email]
);
const handleFormSubmit = async (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
setIsSubmitting(true);
const payload: IEmailCheckData = {
email: data.email,
email: email,
};
onSubmit(payload);
await onSubmit(payload);
setIsSubmitting(false);
};
return (
<form onSubmit={handleSubmit(handleFormSubmit)} className="mx-auto mt-8 space-y-4 w-5/6 sm:w-96">
<form onSubmit={handleFormSubmit} className="mx-auto mt-8 space-y-4 w-5/6 sm:w-96">
<div className="space-y-1">
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="email">
Email
</label>
<Controller
control={control}
name="email"
rules={{
required: "Email is required",
validate: (value) => checkEmailValidity(value) || "Email is invalid",
}}
render={({ field: { value, onChange } }) => (
<>
<div className="relative flex items-center rounded-md bg-onboarding-background-200">
<Input
id="email"
name="email"
type="email"
value={value}
onChange={onChange}
hasError={Boolean(errors.email)}
placeholder="name@company.com"
className="h-[46px] w-full border border-onboarding-border-100 pr-12 placeholder:text-onboarding-text-400"
autoFocus
/>
{value.length > 0 && (
<XCircle
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
onClick={() => onChange("")}
/>
)}
</div>
{errors.email && (
<p className="flex items-center gap-1 text-xs text-red-600 px-0.5">
<CircleAlert height={12} width={12} />
{errors.email.message}
</p>
)}
</>
<div className="relative flex items-center rounded-md bg-onboarding-background-200">
<Input
id="email"
name="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
hasError={Boolean(emailError?.email)}
placeholder="name@company.com"
className="h-[46px] w-full border border-onboarding-border-100 pr-12 placeholder:text-onboarding-text-400"
autoFocus
/>
{email.length > 0 && (
<XCircle
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
onClick={() => setEmail("")}
/>
)}
/>
</div>
{emailError?.email && (
<p className="flex items-center gap-1 text-xs text-red-600 px-0.5">
<CircleAlert height={12} width={12} />
{emailError.email}
</p>
)}
</div>
<Button type="submit" variant="primary" className="w-full" size="lg" disabled={!isValid} loading={isSubmitting}>
<Button
type="submit"
variant="primary"
className="w-full"
size="lg"
disabled={email.length === 0 || Boolean(emailError?.email)}
loading={isSubmitting}
>
Continue
</Button>
</form>

View File

@ -1,5 +1,10 @@
export * from "./sign-up-root";
export * from "./sign-in-root";
export * from "./auth-header";
export * from "./auth-banner";
export * from "./email";
export * from "./forgot-password-popover";
export * from "./password";
export * from "./root";
export * from "./unique-code";

View File

@ -154,7 +154,7 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
</div>
{passwordSupport}
</div>
{mode === EAuthModes.SIGN_UP && getPasswordStrength(passwordFormData.password) >= 3 && (
{mode === EAuthModes.SIGN_UP && (
<div className="space-y-1">
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="confirm_password">
Confirm password

View File

@ -23,16 +23,15 @@ import { WorkspaceService } from "@/services/workspace.service";
const authService = new AuthService();
const workSpaceService = new WorkspaceService();
export enum EAuthModes {
SIGN_IN = "SIGN_IN",
SIGN_UP = "SIGN_UP",
}
export enum EAuthSteps {
EMAIL = "EMAIL",
PASSWORD = "PASSWORD",
UNIQUE_CODE = "UNIQUE_CODE",
OPTIONAL_SET_PASSWORD = "OPTIONAL_SET_PASSWORD",
}
export enum EAuthModes {
SIGN_IN = "SIGN_IN",
SIGN_UP = "SIGN_UP",
}
type Props = {
@ -53,10 +52,6 @@ const Titles = {
header: "Sign in to Plane",
subHeader: "Get back to your projects and make progress",
},
[EAuthSteps.OPTIONAL_SET_PASSWORD]: {
header: "",
subHeader: "",
},
},
[EAuthModes.SIGN_UP]: {
[EAuthSteps.EMAIL]: {
@ -71,10 +66,6 @@ const Titles = {
header: "Create your account",
subHeader: "Progress, visualize, and measure work how it works best for you.",
},
[EAuthSteps.OPTIONAL_SET_PASSWORD]: {
header: "",
subHeader: "",
},
},
};
@ -101,11 +92,11 @@ const getHeaderSubHeader = (
return Titles[mode][step];
};
export const AuthRoot = observer((props: Props) => {
export const SignInAuthRoot = observer((props: Props) => {
const { mode } = props;
//router
const router = useRouter();
const { email: emailParam, invitation_id, slug } = router.query;
const { email: emailParam, invitation_id, slug: workspaceSlug } = router.query;
// states
const [authStep, setAuthStep] = useState<EAuthSteps>(EAuthSteps.EMAIL);
const [email, setEmail] = useState(emailParam ? emailParam.toString() : "");
@ -127,10 +118,10 @@ export const AuthRoot = observer((props: Props) => {
};
useEffect(() => {
if (invitation_id && slug) {
if (invitation_id && workspaceSlug) {
setIsLoading(true);
workSpaceService
.getWorkspaceInvitation(slug.toString(), invitation_id.toString())
.getWorkspaceInvitation(workspaceSlug.toString(), invitation_id.toString())
.then((res) => {
setInvitation(res);
})
@ -141,7 +132,7 @@ export const AuthRoot = observer((props: Props) => {
} else {
setInvitation(undefined);
}
}, [invitation_id, slug]);
}, [invitation_id, workspaceSlug]);
const { header, subHeader } = getHeaderSubHeader(authStep, mode, invitation, email);
@ -205,6 +196,7 @@ export const AuthRoot = observer((props: Props) => {
<p className="font-medium text-onboarding-text-400">{subHeader}</p>
</div>
{authStep === EAuthSteps.EMAIL && <AuthEmailForm defaultEmail={email} onSubmit={handleEmailVerification} />}
{authStep === EAuthSteps.UNIQUE_CODE && (
<UniqueCodeForm
email={email}
@ -216,6 +208,7 @@ export const AuthRoot = observer((props: Props) => {
mode={mode}
/>
)}
{authStep === EAuthSteps.PASSWORD && (
<AuthPasswordForm
email={email}
@ -228,8 +221,7 @@ export const AuthRoot = observer((props: Props) => {
/>
)}
</div>
{isOAuthEnabled && authStep !== EAuthSteps.OPTIONAL_SET_PASSWORD && <OAuthOptions />}
{isOAuthEnabled && <OAuthOptions />}
<TermsAndConditions isSignUp={mode === EAuthModes.SIGN_UP} />
</>
);

View File

@ -0,0 +1,125 @@
import { FC, useState } from "react";
import isEmpty from "lodash/isEmpty";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
// types
import { IEmailCheckData } from "@plane/types";
// ui
import { Spinner, TOAST_TYPE, setToast } from "@plane/ui";
// components
import {
AuthHeader,
AuthBanner,
AuthEmailForm,
AuthPasswordForm,
OAuthOptions,
TermsAndConditions,
UniqueCodeForm,
} from "@/components/account";
// helpers
import { EAuthModes, EAuthSteps, EErrorAlertType, TAuthErrorInfo } from "@/helpers/authentication.helper";
// hooks
import { useInstance } from "@/hooks/store";
// services
import { AuthService } from "@/services/auth.service";
// service initialization
const authService = new AuthService();
export const SignUpAuthRoot: FC = observer(() => {
//router
const router = useRouter();
const { email: emailParam, invitation_id, slug: workspaceSlug } = router.query;
// states
const [authStep, setAuthStep] = useState<EAuthSteps>(EAuthSteps.EMAIL);
const [email, setEmail] = useState(emailParam ? emailParam.toString() : "");
const [isLoading, setIsLoading] = useState(false);
const [errorInfo, setErrorInfo] = useState<TAuthErrorInfo | undefined>(undefined);
// hooks
const { instance } = useInstance();
// derived values
const authMode = EAuthModes.SIGN_UP;
const isSmtpConfigured = instance?.config?.is_smtp_configured;
// const redirectToSignUp = (email: string) => {
// if (isEmpty(email)) router.push({ pathname: "/", query: router.query });
// else router.push({ pathname: "/", query: { ...router.query, email: email } });
// };
// step 1 - email verification
const handleEmailVerification = async (data: IEmailCheckData) => {
setEmail(data.email);
await authService
.signUpEmailCheck(data)
.then(() => {
if (isSmtpConfigured) setAuthStep(EAuthSteps.UNIQUE_CODE);
else setAuthStep(EAuthSteps.PASSWORD);
})
.catch((err) => {
console.log("error", err);
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: err?.error_message ?? "Something went wrong. Please try again.",
});
});
};
const isOAuthEnabled =
instance?.config && (instance?.config?.is_google_enabled || instance?.config?.is_github_enabled);
if (isLoading)
return (
<div className="flex h-full w-full items-center justify-center">
<Spinner />
</div>
);
return (
<>
<div className="relative max-w-lg mx-auto flex flex-col space-y-6">
<AuthHeader
workspaceSlug={workspaceSlug?.toString() || undefined}
invitationId={invitation_id?.toString() || undefined}
invitationEmail={emailParam?.toString() || undefined}
authMode={EAuthModes.SIGN_UP}
currentAuthStep={authStep}
handleLoader={setIsLoading}
/>
{errorInfo && errorInfo?.type === EErrorAlertType.BANNER_ALERT && (
<AuthBanner bannerData={errorInfo} handleBannerData={(value) => setErrorInfo(value)} />
)}
{authStep === EAuthSteps.EMAIL && <AuthEmailForm defaultEmail={email} onSubmit={handleEmailVerification} />}
{authStep === EAuthSteps.UNIQUE_CODE && (
<UniqueCodeForm
email={email}
handleEmailClear={() => {
setEmail("");
setAuthStep(EAuthSteps.EMAIL);
}}
submitButtonText="Continue"
mode={authMode}
/>
)}
{authStep === EAuthSteps.PASSWORD && (
<AuthPasswordForm
email={email}
handleEmailClear={() => {
setEmail("");
setAuthStep(EAuthSteps.EMAIL);
}}
handleStepChange={(step) => setAuthStep(step)}
mode={authMode}
/>
)}
{isOAuthEnabled && <OAuthOptions />}
<TermsAndConditions isSignUp={authMode === EAuthModes.SIGN_UP} />
</div>
</>
);
});

View File

@ -11,7 +11,7 @@ import { API_BASE_URL } from "@/helpers/common.helper";
import useTimer from "@/hooks/use-timer";
// services
import { AuthService } from "@/services/auth.service";
import { EAuthModes } from "./root";
import { EAuthModes } from "./sign-up-root";
type Props = {
email: string;

View File

@ -6,7 +6,7 @@ import Link from "next/link";
import { useTheme } from "next-themes";
import { Spinner } from "@plane/ui";
// components
import { AuthRoot, EAuthModes } from "@/components/account";
import { SignUpAuthRoot } from "@/components/account";
import { PageHead } from "@/components/core";
// constants
import { NAVIGATE_TO_SIGNIN } from "@/constants/event-tracker";
@ -69,7 +69,7 @@ export const SignUpView = observer(() => {
</div>
<div className="mx-auto h-full">
<div className="h-full overflow-auto px-7 pb-56 pt-4 sm:px-0">
<AuthRoot mode={EAuthModes.SIGN_UP} />
<SignUpAuthRoot />
</div>
</div>
</div>

View File

@ -1,53 +1,108 @@
export enum ESignUpEMailCheck {
INSTANCE_NOT_CONFIGURED = "INSTANCE_NOT_CONFIGURED",
USER_ALREADY_EXIST = "USER_ALREADY_EXIST",
export enum EAuthModes {
SIGN_IN = "SIGN_IN",
SIGN_UP = "SIGN_UP",
}
export enum ESignUp {
export enum EAuthSteps {
EMAIL = "EMAIL",
PASSWORD = "PASSWORD",
UNIQUE_CODE = "UNIQUE_CODE",
}
export enum EAuthenticationErrorCodes {
// alert errors
INSTANCE_NOT_CONFIGURED = "INSTANCE_NOT_CONFIGURED",
REQUIRED_EMAIL_PASSWORD = "REQUIRED_EMAIL_PASSWORD",
SMTP_NOT_CONFIGURED = "SMTP_NOT_CONFIGURED",
AUTHENTICATION_FAILED = "AUTHENTICATION_FAILED",
INVALID_TOKEN = "INVALID_TOKEN",
EXPIRED_TOKEN = "EXPIRED_TOKEN",
IMPROPERLY_CONFIGURED = "IMPROPERLY_CONFIGURED",
OAUTH_PROVIDER_ERROR = "OAUTH_PROVIDER_ERROR",
// banner errors
INVALID_EMAIL = "INVALID_EMAIL",
INVALID_PASSWORD = "INVALID_PASSWORD",
USER_DOES_NOT_EXIST = "USER_DOES_NOT_EXIST",
ADMIN_ALREADY_EXIST = "ADMIN_ALREADY_EXIST",
USER_ALREADY_EXIST = "USER_ALREADY_EXIST",
}
export enum ESignInEMailCheck {
INSTANCE_NOT_CONFIGURED = "INSTANCE_NOT_CONFIGURED",
// inline errors from backend
REQUIRED_EMAIL_PASSWORD_FIRST_NAME = "REQUIRED_EMAIL_PASSWORD_FIRST_NAME",
REQUIRED_EMAIL_PASSWORD = "REQUIRED_EMAIL_PASSWORD",
INVALID_EMAIL = "INVALID_EMAIL",
USER_ALREADY_EXIST = "USER_ALREADY_EXIST",
EMAIL_CODE_REQUIRED = "EMAIL_CODE_REQUIRED",
// inline local errors
INLINE_EMAIL = "INLINE_EMAIL",
INLINE_PASSWORD = "INLINE_PASSWORD",
INLINE_FIRST_NAME = "INLINE_FIRST_NAME",
INLINE_EMAIL_CODE = "INLINE_EMAIL_CODE",
}
export enum ESignIn {
INSTANCE_NOT_CONFIGURED = "INSTANCE_NOT_CONFIGURED",
REQUIRED_EMAIL_PASSWORD = "REQUIRED_EMAIL_PASSWORD",
INVALID_EMAIL = "INVALID_EMAIL",
USER_ALREADY_EXIST = "USER_ALREADY_EXIST",
}
export type TErrorTypes = ESignUpEMailCheck | ESignUp | ESignInEMailCheck | ESignIn;
export enum EErrorAlertType {
BANNER_ALERT = "BANNER_ALERT",
TOAST_ALERT = "TOAST_ALERT",
INLINE_FIRST_NAME = "INLINE_FIRST_NAME",
INLINE_EMAIL = "INLINE_EMAIL",
INLINE_PASSWORD = "INLINE_PASSWORD",
INLINE_EMAIL_CODE = "INLINE_EMAIL_CODE",
}
export const errorHandler = (
errorType: TErrorTypes,
errorMessage: string | undefined
): { type: EErrorAlertType | undefined; message: string | undefined } => {
const errorPayload = {
type: undefined,
message: errorMessage || undefined,
};
const signUpErrorTypes = [""];
const signInErrorTypes = [""];
export type TAuthErrorInfo = { type: EErrorAlertType; message: string };
console.log("errorType", errorType);
console.log("signUpErrorTypes", signUpErrorTypes);
console.log("signInErrorTypes", signInErrorTypes);
export const errorHandler = (errorType: EAuthenticationErrorCodes, errorMessage: string | undefined) => {
const toastAlertErrorCodes = [
EAuthenticationErrorCodes.INSTANCE_NOT_CONFIGURED,
EAuthenticationErrorCodes.SMTP_NOT_CONFIGURED,
EAuthenticationErrorCodes.AUTHENTICATION_FAILED,
EAuthenticationErrorCodes.INVALID_TOKEN,
EAuthenticationErrorCodes.EXPIRED_TOKEN,
EAuthenticationErrorCodes.IMPROPERLY_CONFIGURED,
EAuthenticationErrorCodes.OAUTH_PROVIDER_ERROR,
];
const bannerAlertErrorCodes = [
EAuthenticationErrorCodes.USER_DOES_NOT_EXIST,
EAuthenticationErrorCodes.ADMIN_ALREADY_EXIST,
EAuthenticationErrorCodes.USER_ALREADY_EXIST,
];
const inlineFirstNameErrorCodes = [EAuthenticationErrorCodes.INLINE_FIRST_NAME];
const inlineEmailErrorCodes = [EAuthenticationErrorCodes.INLINE_EMAIL];
const inlineEmailCodeErrorCodes = [EAuthenticationErrorCodes.INLINE_EMAIL_CODE];
const inlinePasswordErrorCodes = [EAuthenticationErrorCodes.INLINE_PASSWORD];
let errorPayload: TAuthErrorInfo | undefined = undefined;
if (toastAlertErrorCodes.includes(errorType))
errorPayload = {
type: EErrorAlertType.TOAST_ALERT,
message: errorMessage || "Something went wrong",
};
if (bannerAlertErrorCodes.includes(errorType))
errorPayload = {
type: EErrorAlertType.BANNER_ALERT,
message: errorMessage || "Something went wrong",
};
if (inlineFirstNameErrorCodes.includes(errorType))
errorPayload = {
type: EErrorAlertType.INLINE_FIRST_NAME,
message: errorMessage || "Something went wrong",
};
if (inlineEmailErrorCodes.includes(errorType))
errorPayload = {
type: EErrorAlertType.INLINE_EMAIL,
message: errorMessage || "Something went wrong",
};
if (inlinePasswordErrorCodes.includes(errorType))
errorPayload = {
type: EErrorAlertType.INLINE_PASSWORD,
message: errorMessage || "Something went wrong",
};
if (inlineEmailCodeErrorCodes.includes(errorType))
errorPayload = {
type: EErrorAlertType.INLINE_EMAIL_CODE,
message: errorMessage || "Something went wrong",
};
return errorPayload;
};

View File

@ -14,7 +14,7 @@ import { resolveGeneralTheme } from "@/helpers/theme.helper";
// hooks
import { useInstance, useWorkspace, useUser } from "@/hooks/store";
// layouts
import InstanceLayout from "@/layouts/instance-layout";
import InstanceLayout from "@/lib/wrappers/instance-wrapper";
// dynamic imports
const StoreWrapper = dynamic(() => import("@/lib/wrappers/store-wrapper"), { ssr: false });
const PostHogProvider = dynamic(() => import("@/lib/posthog-provider"), { ssr: false });

View File

@ -0,0 +1,14 @@
import { FC, ReactNode } from "react";
type TPageType = "public" | "onboarding" | "private";
type TAuthenticationWrapper = {
children: ReactNode;
pageType: TPageType;
};
export const AuthenticationWrapper: FC<TAuthenticationWrapper> = (props) => {
const { children, pageType } = props;
return <div key={pageType}>{children}</div>;
};

View File

@ -1,4 +1,4 @@
import { FC, ReactNode, useState } from "react";
import { FC, ReactNode } from "react";
import { observer } from "mobx-react";
import useSWR from "swr";
// ui
@ -8,17 +8,14 @@ import { InstanceNotReady } from "@/components/instance";
// hooks
import { useInstance } from "@/hooks/store";
type TInstanceLayout = {
type TInstanceWrapper = {
children: ReactNode;
};
const InstanceLayout: FC<TInstanceLayout> = observer((props) => {
const InstanceWrapper: FC<TInstanceWrapper> = observer((props) => {
const { children } = props;
// store
const { isLoading, instance, error, fetchInstanceInfo } = useInstance();
// states
const [isGodModeEnabled, setIsGodModeEnabled] = useState(false);
const handleGodModeStateChange = (state: boolean) => setIsGodModeEnabled(state);
useSWR("INSTANCE_INFORMATION", () => fetchInstanceInfo(), {
revalidateOnFocus: false,
@ -44,11 +41,9 @@ const InstanceLayout: FC<TInstanceLayout> = observer((props) => {
if (error && !error?.data?.is_activated) return <InstanceNotReady isGodModeEnabled={false} />;
// instance is not ready and setup is not done
if (instance?.instance?.is_setup_done === false)
// if (isGodModeEnabled) return <MiniGodModeForm />;
return <InstanceNotReady isGodModeEnabled handleGodModeStateChange={handleGodModeStateChange} />;
if (instance?.instance?.is_setup_done === false) return <InstanceNotReady isGodModeEnabled />;
return <>{children}</>;
});
export default InstanceLayout;
export default InstanceWrapper;

View File

@ -6,7 +6,7 @@ import Link from "next/link";
import { useTheme } from "next-themes";
import { Spinner } from "@plane/ui";
// components
import { AuthRoot, EAuthModes } from "@/components/account";
import { SignInAuthRoot } from "@/components/account";
import { PageHead } from "@/components/core";
// constants
import { NAVIGATE_TO_SIGNUP } from "@/constants/event-tracker";
@ -74,7 +74,7 @@ const SignInPage: NextPageWithLayout = observer(() => {
</div>
<div className="mx-auto h-full">
<div className="h-full overflow-auto px-7 pb-56 pt-4 sm:px-0">
<AuthRoot mode={EAuthModes.SIGN_IN} />
<SignInAuthRoot />
</div>
</div>
</div>