chore: handled signin workflow

This commit is contained in:
gurusainath 2024-04-30 19:30:42 +05:30
parent e8711dc4cc
commit 6c84b4f490
12 changed files with 275 additions and 399 deletions

View File

@ -240,13 +240,15 @@ export const InstanceSignUpForm: FC = (props) => {
</div>
<div className="relative flex items-center pt-2 gap-2">
<Checkbox
id="is_telemetry_enabled"
name="is_telemetry_enabled"
value={formData.is_telemetry_enabled ? "True" : "False"}
onChange={() => handleFormChange("is_telemetry_enabled", !formData.is_telemetry_enabled)}
checked={formData.is_telemetry_enabled}
/>
<div>
<Checkbox
id="is_telemetry_enabled"
name="is_telemetry_enabled"
value={formData.is_telemetry_enabled ? "True" : "False"}
onChange={() => handleFormChange("is_telemetry_enabled", !formData.is_telemetry_enabled)}
checked={formData.is_telemetry_enabled}
/>
</div>
<label className="text-sm text-custom-text-300 font-medium cursor-pointer" htmlFor="is_telemetry_enabled">
Allow Plane to anonymously collect usage events.
</label>

View File

@ -2,7 +2,6 @@
import { FC, ReactNode } from "react";
import Image from "next/image";
import { usePathname } from "next/navigation";
// logo
import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png";
@ -12,9 +11,6 @@ type TDefaultLayout = {
export const DefaultLayout: FC<TDefaultLayout> = (props) => {
const { children } = props;
const pathname = usePathname();
console.log("pathname", pathname);
return (
<div className="relative h-screen max-h-max w-full overflow-hidden overflow-y-auto flex flex-col">

View File

@ -13,8 +13,10 @@ export const AuthBanner: FC<TAuthBanner> = (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="relative inline-flex items-center p-2 rounded-md gap-2 border border-custom-primary-100/50 bg-custom-primary-100/10">
<div className="w-4 h-4 flex-shrink-0 relative flex justify-center items-center">
<Info size={16} className="text-custom-primary-100" />
</div>
<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"

View File

@ -6,10 +6,11 @@ import { Eye, EyeOff, XCircle } from "lucide-react";
// ui
import { Button, Input } from "@plane/ui";
// components
import { EAuthModes, EAuthSteps, ForgotPasswordPopover, PasswordStrengthMeter } from "@/components/account";
import { ForgotPasswordPopover, PasswordStrengthMeter } from "@/components/account";
// constants
import { FORGOT_PASSWORD } from "@/constants/event-tracker";
// helpers
import { EAuthModes, EAuthSteps } from "@/helpers/authentication.helper";
import { API_BASE_URL } from "@/helpers/common.helper";
import { getPasswordStrength } from "@/helpers/password.helper";
// hooks
@ -94,51 +95,78 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
);
return (
<>
<form
className="mx-auto mt-5 space-y-4 w-5/6 sm:w-96"
method="POST"
action={`${API_BASE_URL}/auth/${mode === EAuthModes.SIGN_IN ? "sign-in" : "sign-up"}/`}
>
<input type="hidden" name="csrfmiddlewaretoken" value={csrfToken} />
<div className="space-y-1">
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="email">
Email
</label>
<div className="relative flex items-center rounded-md bg-onboarding-background-200">
<Input
id="email"
name="email"
type="email"
value={passwordFormData.email}
onChange={(e) => handleFormChange("email", e.target.value)}
// 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"
<form
className="mx-auto mt-5 space-y-4 w-5/6 sm:w-96"
method="POST"
action={`${API_BASE_URL}/auth/${mode === EAuthModes.SIGN_IN ? "sign-in" : "sign-up"}/`}
>
<input type="hidden" name="csrfmiddlewaretoken" value={csrfToken} />
<div className="space-y-1">
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="email">
Email
</label>
<div className="relative flex items-center rounded-md bg-onboarding-background-200">
<Input
id="email"
name="email"
type="email"
value={passwordFormData.email}
onChange={(e) => handleFormChange("email", e.target.value)}
// 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"
/>
{passwordFormData.email.length > 0 && (
<XCircle
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
onClick={handleEmailClear}
/>
{passwordFormData.email.length > 0 && (
<XCircle
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
onClick={handleEmailClear}
/>
)}
</div>
)}
</div>
</div>
<div className="space-y-1">
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="password">
{mode === EAuthModes.SIGN_IN ? "Password" : "Set a password"}
</label>
<div className="relative flex items-center rounded-md bg-onboarding-background-200">
<Input
type={showPassword ? "text" : "password"}
name="password"
value={passwordFormData.password}
onChange={(e) => handleFormChange("password", e.target.value)}
placeholder="Enter password"
className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
onFocus={() => setIsPasswordInputFocused(true)}
onBlur={() => setIsPasswordInputFocused(false)}
autoFocus
/>
{showPassword ? (
<EyeOff
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
onClick={() => setShowPassword(false)}
/>
) : (
<Eye
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
onClick={() => setShowPassword(true)}
/>
)}
</div>
{passwordSupport}
</div>
{mode === EAuthModes.SIGN_UP && (
<div className="space-y-1">
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="password">
{mode === EAuthModes.SIGN_IN ? "Password" : "Set a password"}
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="confirm_password">
Confirm password
</label>
<div className="relative flex items-center rounded-md bg-onboarding-background-200">
<Input
type={showPassword ? "text" : "password"}
name="password"
value={passwordFormData.password}
onChange={(e) => handleFormChange("password", e.target.value)}
placeholder="Enter password"
name="confirm_password"
value={passwordFormData.confirm_password}
onChange={(e) => handleFormChange("confirm_password", e.target.value)}
placeholder="Confirm password"
className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
onFocus={() => setIsPasswordInputFocused(true)}
onBlur={() => setIsPasswordInputFocused(false)}
autoFocus
/>
{showPassword ? (
<EyeOff
@ -152,64 +180,35 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
/>
)}
</div>
{passwordSupport}
</div>
{mode === EAuthModes.SIGN_UP && (
<div className="space-y-1">
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="confirm_password">
Confirm password
</label>
<div className="relative flex items-center rounded-md bg-onboarding-background-200">
<Input
type={showPassword ? "text" : "password"}
name="confirm_password"
value={passwordFormData.confirm_password}
onChange={(e) => handleFormChange("confirm_password", e.target.value)}
placeholder="Confirm password"
className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
/>
{showPassword ? (
<EyeOff
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
onClick={() => setShowPassword(false)}
/>
) : (
<Eye
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
onClick={() => setShowPassword(true)}
/>
)}
</div>
{!!passwordFormData.confirm_password && passwordFormData.password !== passwordFormData.confirm_password && (
<span className="text-sm text-red-500">Passwords don{"'"}t match</span>
)}
</div>
)}
<div className="space-y-2.5">
{mode === EAuthModes.SIGN_IN ? (
<>
<Button type="submit" variant="primary" className="w-full" size="lg" disabled={isButtonDisabled}>
{isSmtpConfigured ? "Continue" : "Go to workspace"}
</Button>
{instance && isSmtpConfigured && (
<Button
type="button"
onClick={redirectToUniqueCodeLogin}
variant="outline-primary"
className="w-full"
size="lg"
>
Sign in with unique code
</Button>
)}
</>
) : (
<Button type="submit" variant="primary" className="w-full" size="lg" disabled={isButtonDisabled}>
Create account
</Button>
{!!passwordFormData.confirm_password && passwordFormData.password !== passwordFormData.confirm_password && (
<span className="text-sm text-red-500">Passwords don{"'"}t match</span>
)}
</div>
</form>
</>
)}
<div className="space-y-2.5">
{mode === EAuthModes.SIGN_IN ? (
<>
<Button type="submit" variant="primary" className="w-full" size="lg" disabled={isButtonDisabled}>
{isSmtpConfigured ? "Continue" : "Go to workspace"}
</Button>
{instance && isSmtpConfigured && (
<Button
type="button"
onClick={redirectToUniqueCodeLogin}
variant="outline-primary"
className="w-full"
size="lg"
>
Sign in with unique code
</Button>
)}
</>
) : (
<Button type="submit" variant="primary" className="w-full" size="lg" disabled={isButtonDisabled}>
Create account
</Button>
)}
</div>
</form>
);
});

View File

@ -1,175 +1,68 @@
import React, { useEffect, useState } from "react";
import isEmpty from "lodash/isEmpty";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
// types
import { IEmailCheckData, IWorkspaceMemberInvitation } from "@plane/types";
// ui
import { IEmailCheckData } from "@plane/types";
import { Spinner, TOAST_TYPE, setToast } from "@plane/ui";
// components
import {
AuthHeader,
AuthBanner,
AuthEmailForm,
AuthPasswordForm,
OAuthOptions,
TermsAndConditions,
UniqueCodeForm,
} from "@/components/account";
import { WorkspaceLogo } from "@/components/workspace/logo";
// helpers
import {
EAuthModes,
EAuthSteps,
EAuthenticationErrorCodes,
EErrorAlertType,
TAuthErrorInfo,
authErrorHandler,
} from "@/helpers/authentication.helper";
// hooks
import { useInstance } from "@/hooks/store";
// services
import { AuthService } from "@/services/auth.service";
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",
}
type Props = {
mode: EAuthModes;
};
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 getHeaderSubHeader = (
step: EAuthSteps,
mode: EAuthModes,
invitation?: IWorkspaceMemberInvitation | undefined,
email?: string
) => {
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];
};
export const SignInAuthRoot = observer((props: Props) => {
const { mode } = props;
export const SignInAuthRoot = observer(() => {
//router
const router = useRouter();
const { email: emailParam, invitation_id, slug: workspaceSlug } = router.query;
const { email: emailParam, invitation_id, slug: workspaceSlug, error_code, error_message } = router.query;
// states
const [authStep, setAuthStep] = useState<EAuthSteps>(EAuthSteps.EMAIL);
const [email, setEmail] = useState(emailParam ? emailParam.toString() : "");
const [invitation, setInvitation] = useState<IWorkspaceMemberInvitation | undefined>(undefined);
const [isLoading, setIsLoading] = useState(false);
const [errorInfo, setErrorInfo] = useState<TAuthErrorInfo | undefined>(undefined);
// hooks
const { instance } = useInstance();
// derived values
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 } });
};
const redirectToSignIn = (email: string) => {
if (isEmpty(email)) router.push({ pathname: "/accounts/sign-in", query: router.query });
else router.push({ pathname: "/accounts/sign-in", query: { ...router.query, email: email } });
};
const authMode = EAuthModes.SIGN_IN;
useEffect(() => {
if (invitation_id && workspaceSlug) {
setIsLoading(true);
workSpaceService
.getWorkspaceInvitation(workspaceSlug.toString(), invitation_id.toString())
.then((res) => {
setInvitation(res);
})
.catch(() => {
setInvitation(undefined);
})
.finally(() => setIsLoading(false));
} else {
setInvitation(undefined);
if (error_code && error_message) {
const errorhandler = authErrorHandler(
error_code?.toString() as EAuthenticationErrorCodes,
error_message?.toString()
);
if (errorhandler) setErrorInfo(errorhandler);
}
}, [invitation_id, workspaceSlug]);
const { header, subHeader } = getHeaderSubHeader(authStep, mode, invitation, email);
}, [error_code, error_message]);
// step 1 submit handler- email verification
const handleEmailVerification = async (data: IEmailCheckData) => {
setEmail(data.email);
const emailCheck = mode === EAuthModes.SIGN_UP ? authService.signUpEmailCheck : authService.signInEmailCheck;
await emailCheck(data)
.then((res) => {
if (mode === EAuthModes.SIGN_IN && !res.is_password_autoset) {
setAuthStep(EAuthSteps.PASSWORD);
} else {
if (isSmtpConfigured) {
setAuthStep(EAuthSteps.UNIQUE_CODE);
} else {
if (mode === EAuthModes.SIGN_IN) {
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Unable to process request please contact Administrator to reset password",
});
} else {
setAuthStep(EAuthSteps.PASSWORD);
}
}
}
await authService
.signInEmailCheck(data)
.then(() => {
setAuthStep(EAuthSteps.PASSWORD);
})
.catch((err) => {
if (err?.error_code === "USER_DOES_NOT_EXIST") {
redirectToSignUp(data.email);
return;
} else if (err?.error_code === "USER_ALREADY_EXIST") {
redirectToSignIn(data.email);
return;
}
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
@ -190,13 +83,19 @@ export const SignInAuthRoot = observer((props: Props) => {
return (
<>
<div className="mx-auto flex flex-col">
<div className="text-center space-y-1 py-4 mx-auto sm:w-96">
<h3 className="flex gap-4 justify-center text-3xl font-bold text-onboarding-text-100">{header}</h3>
<p className="font-medium text-onboarding-text-400">{subHeader}</p>
</div>
<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={email || undefined}
authMode={EAuthModes.SIGN_IN}
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}
@ -205,10 +104,9 @@ export const SignInAuthRoot = observer((props: Props) => {
setAuthStep(EAuthSteps.EMAIL);
}}
submitButtonText="Continue"
mode={mode}
mode={authMode}
/>
)}
{authStep === EAuthSteps.PASSWORD && (
<AuthPasswordForm
email={email}
@ -217,12 +115,12 @@ export const SignInAuthRoot = observer((props: Props) => {
setAuthStep(EAuthSteps.EMAIL);
}}
handleStepChange={(step) => setAuthStep(step)}
mode={mode}
mode={authMode}
/>
)}
{isOAuthEnabled && <OAuthOptions />}
<TermsAndConditions isSignUp={false} />
</div>
{isOAuthEnabled && <OAuthOptions />}
<TermsAndConditions isSignUp={mode === EAuthModes.SIGN_UP} />
</>
);
});

View File

@ -1,5 +1,4 @@
import { FC, useState } from "react";
import isEmpty from "lodash/isEmpty";
import { FC, useEffect, useState } from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
// types
@ -17,7 +16,14 @@ import {
UniqueCodeForm,
} from "@/components/account";
// helpers
import { EAuthModes, EAuthSteps, EErrorAlertType, TAuthErrorInfo } from "@/helpers/authentication.helper";
import {
EAuthModes,
EAuthSteps,
EAuthenticationErrorCodes,
EErrorAlertType,
TAuthErrorInfo,
authErrorHandler,
} from "@/helpers/authentication.helper";
// hooks
import { useInstance } from "@/hooks/store";
// services
@ -29,7 +35,7 @@ const authService = new AuthService();
export const SignUpAuthRoot: FC = observer(() => {
//router
const router = useRouter();
const { email: emailParam, invitation_id, slug: workspaceSlug } = router.query;
const { email: emailParam, invitation_id, slug: workspaceSlug, error_code, error_message } = router.query;
// states
const [authStep, setAuthStep] = useState<EAuthSteps>(EAuthSteps.EMAIL);
const [email, setEmail] = useState(emailParam ? emailParam.toString() : "");
@ -41,12 +47,17 @@ export const SignUpAuthRoot: FC = observer(() => {
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 } });
// };
useEffect(() => {
if (error_code && error_message) {
const errorhandler = authErrorHandler(
error_code?.toString() as EAuthenticationErrorCodes,
error_message?.toString()
);
if (errorhandler) setErrorInfo(errorhandler);
}
}, [error_code, error_message]);
// step 1 - email verification
// email verification
const handleEmailVerification = async (data: IEmailCheckData) => {
setEmail(data.email);
await authService
@ -55,12 +66,16 @@ export const SignUpAuthRoot: FC = observer(() => {
if (isSmtpConfigured) setAuthStep(EAuthSteps.UNIQUE_CODE);
else setAuthStep(EAuthSteps.PASSWORD);
})
.catch((err) => {
console.log("error", err);
.catch((error) => {
const errorhandler = authErrorHandler(error?.error_code, error?.error_message);
if (errorhandler) {
setErrorInfo(errorhandler);
return;
}
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: err?.error_message ?? "Something went wrong. Please try again.",
message: error?.error_message ?? "Something went wrong. Please try again.",
});
});
};
@ -76,50 +91,43 @@ export const SignUpAuthRoot: FC = observer(() => {
);
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}
<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={email || 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}
/>
{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>
</>
)}
{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

@ -1,17 +1,14 @@
import React, { useEffect, useState } from "react";
import { CircleCheck, XCircle } from "lucide-react";
// types
import { IEmailCheckData } from "@plane/types";
// ui
import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui";
// constants
// helpers
import { EAuthModes } from "@/helpers/authentication.helper";
import { API_BASE_URL } from "@/helpers/common.helper";
// hooks
import useTimer from "@/hooks/use-timer";
// services
import { AuthService } from "@/services/auth.service";
import { EAuthModes } from "./sign-up-root";
type Props = {
email: string;

View File

@ -1,45 +1,43 @@
import { FC } from "react";
import Image from "next/image";
import Link from "next/link";
import { useTheme } from "next-themes";
// icons
import { UserCog2 } from "lucide-react";
// ui
import { getButtonStyling } from "@plane/ui";
import { Button } from "@plane/ui";
// images
import instanceNotReady from "public/instance/plane-instance-not-ready.webp";
import PlaneBlackLogo from "public/plane-logos/black-horizontal-with-blue-logo.svg";
import PlaneWhiteLogo from "public/plane-logos/white-horizontal-with-blue-logo.svg";
type TInstanceNotReady = {
isGodModeEnabled: boolean;
handleGodModeStateChange?: (state: boolean) => void;
};
export const InstanceNotReady: FC<TInstanceNotReady> = () => {
// const { isGodModeEnabled, handleGodModeStateChange } = props;
import PlaneBlackLogo from "@/public/plane-logos/black-horizontal-with-blue-logo.svg";
import PlaneWhiteLogo from "@/public/plane-logos/white-horizontal-with-blue-logo.svg";
import PlaneTakeOffImage from "@/public/plane-takeoff.png";
export const InstanceNotReady: FC = () => {
const { resolvedTheme } = useTheme();
const planeLogo = resolvedTheme === "dark" ? PlaneWhiteLogo : PlaneBlackLogo;
return (
<div className="h-screen w-full overflow-y-auto bg-onboarding-gradient-100">
<div className="h-full w-full pt-24">
<div className="mx-auto h-full rounded-t-md border-x border-t border-custom-border-100 bg-onboarding-gradient-100 px-4 pt-4 shadow-sm sm:w-4/5 md:w-2/3">
<div className="relative h-full rounded-t-md bg-onboarding-gradient-200 px-7 sm:px-0">
<div className="flex items-center justify-center py-10">
<Image src={planeLogo} className="h-[44px] w-full" alt="Plane logo" />
<div className="relative h-screen max-h-max w-full overflow-hidden overflow-y-auto flex flex-col">
<div className="flex-shrink-0 h-[120px]">
<div className="relative h-full container mx-auto px-5 lg:px-0 flex items-center justify-between gap-5 z-50">
<div className="flex items-center gap-x-2">
<Image src={planeLogo} className="h-[24px] w-full" alt="Plane logo" />
</div>
</div>
</div>
<div className="w-full flex-grow">
<div className="h-full w-full relative container px-5 mx-auto flex justify-center items-center">
<div className="w-auto max-w-2xl relative space-y-8 py-10">
<div className="relative flex flex-col justify-center items-center space-y-4">
<h1 className="text-3xl font-bold pb-3">Welcome aboard Plane!</h1>
<Image src={PlaneTakeOffImage} alt="Plane Logo" />
<p className="font-medium text-base text-custom-text-400">
Get started by setting up your instance and workspace
</p>
</div>
<div className="mt-20">
<Image src={instanceNotReady} className="w-full" alt="Instance not ready" />
</div>
<div className="flex w-full flex-col items-center gap-5 py-12 pb-20">
<h3 className="text-2xl font-medium">Your Plane instance isn{"'"}t ready yet</h3>
<p className="text-sm">Ask your Instance Admin to complete set-up first.</p>
<a href="/god-mode" className={`${getButtonStyling("primary", "md")} mt-4`}>
<UserCog2 className="h-3.5 w-3.5" />
Get started
</a>
<div>
<Link href={"/god-mode/"}>
<Button size="lg" className="w-full">
Get started
</Button>
</Link>
</div>
</div>
</div>

View File

@ -46,7 +46,10 @@ export enum EErrorAlertType {
export type TAuthErrorInfo = { type: EErrorAlertType; message: string };
export const errorHandler = (errorType: EAuthenticationErrorCodes, errorMessage: string | undefined) => {
export const authErrorHandler = (
errorCode: EAuthenticationErrorCodes,
errorMessage: string | undefined
): TAuthErrorInfo | undefined => {
const toastAlertErrorCodes = [
EAuthenticationErrorCodes.INSTANCE_NOT_CONFIGURED,
EAuthenticationErrorCodes.SMTP_NOT_CONFIGURED,
@ -66,43 +69,41 @@ export const errorHandler = (errorType: EAuthenticationErrorCodes, errorMessage:
const inlineEmailCodeErrorCodes = [EAuthenticationErrorCodes.INLINE_EMAIL_CODE];
const inlinePasswordErrorCodes = [EAuthenticationErrorCodes.INLINE_PASSWORD];
let errorPayload: TAuthErrorInfo | undefined = undefined;
if (toastAlertErrorCodes.includes(errorType))
errorPayload = {
if (toastAlertErrorCodes.includes(errorCode))
return {
type: EErrorAlertType.TOAST_ALERT,
message: errorMessage || "Something went wrong",
message: errorMessage || "Something went wrong. Please try again.",
};
if (bannerAlertErrorCodes.includes(errorType))
errorPayload = {
if (bannerAlertErrorCodes.includes(errorCode))
return {
type: EErrorAlertType.BANNER_ALERT,
message: errorMessage || "Something went wrong",
message: errorMessage || "Something went wrong. Please try again.",
};
if (inlineFirstNameErrorCodes.includes(errorType))
errorPayload = {
if (inlineFirstNameErrorCodes.includes(errorCode))
return {
type: EErrorAlertType.INLINE_FIRST_NAME,
message: errorMessage || "Something went wrong",
message: errorMessage || "Something went wrong. Please try again.",
};
if (inlineEmailErrorCodes.includes(errorType))
errorPayload = {
if (inlineEmailErrorCodes.includes(errorCode))
return {
type: EErrorAlertType.INLINE_EMAIL,
message: errorMessage || "Something went wrong",
message: errorMessage || "Something went wrong. Please try again.",
};
if (inlinePasswordErrorCodes.includes(errorType))
errorPayload = {
if (inlinePasswordErrorCodes.includes(errorCode))
return {
type: EErrorAlertType.INLINE_PASSWORD,
message: errorMessage || "Something went wrong",
message: errorMessage || "Something went wrong. Please try again.",
};
if (inlineEmailCodeErrorCodes.includes(errorType))
errorPayload = {
if (inlineEmailCodeErrorCodes.includes(errorCode))
return {
type: EErrorAlertType.INLINE_EMAIL_CODE,
message: errorMessage || "Something went wrong",
message: errorMessage || "Something went wrong. Please try again.",
};
return errorPayload;
return undefined;
};

View File

@ -37,11 +37,8 @@ const InstanceWrapper: FC<TInstanceWrapper> = observer((props) => {
</div>
);
// checking if the instance is activated or not
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) return <InstanceNotReady isGodModeEnabled />;
if (instance?.instance?.is_setup_done === false) return <InstanceNotReady />;
return <>{children}</>;
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View File

@ -16,7 +16,6 @@ type TError = {
export interface IInstanceStore {
// issues
isLoading: boolean;
instanceNotReady: any | undefined;
instance: IInstance | undefined;
error: TError | undefined;
// action
@ -25,7 +24,6 @@ export interface IInstanceStore {
export class InstanceStore implements IInstanceStore {
isLoading: boolean = true;
instanceNotReady: any | undefined = undefined;
instance: IInstance | undefined = undefined;
error: TError | undefined = undefined;
// services
@ -49,33 +47,13 @@ export class InstanceStore implements IInstanceStore {
*/
fetchInstanceInfo = async () => {
try {
runInAction(() => {
this.isLoading = true;
this.error = undefined;
});
this.isLoading = true;
this.error = undefined;
const instance = await this.instanceService.getInstanceInfo();
const isInstanceNotSetup = (instance: IInstance) => "is_activated" in instance && "is_setup_done" in instance;
if (isInstanceNotSetup(instance)) {
runInAction(() => {
this.isLoading = false;
this.error = {
status: "success",
message: "Instance is not created in the backend",
data: {
is_activated: instance?.instance?.is_activated,
is_setup_done: instance?.instance?.is_setup_done,
},
};
});
} else {
runInAction(() => {
this.isLoading = false;
this.instance = instance;
});
}
runInAction(() => {
this.isLoading = false;
this.instance = instance;
});
} catch (error) {
runInAction(() => {
this.isLoading = false;