chore: handled authentication in space and web apps

This commit is contained in:
guru_sainath 2024-05-08 12:25:36 +05:30
parent 61f47258ee
commit f0faf028c2
52 changed files with 1089 additions and 681 deletions

View File

@ -120,7 +120,7 @@ class MagicCodeProvider(CredentialAdapter):
"INVALID_MAGIC_CODE"
],
error_message="INVALID_MAGIC_CODE",
payload={"email": str(self.key)},
payload={"email": str(email)},
)
else:
raise AuthenticationException(

View File

@ -8,7 +8,7 @@ import { Button, Input, Spinner, TOAST_TYPE, setToast } from "@plane/ui";
// components
import { UserImageUploadModal } from "@/components/accounts";
// hooks
import { useMobxStore } from "@/lib/mobx/store-provider";
import { useMobxStore } from "@/hooks/store";
// services
import fileService from "@/services/file.service";

View File

@ -6,7 +6,7 @@ import { useRouter } from "next/router";
import { IssueBlockDueDate } from "@/components/issues/board-views/block-due-date";
import { IssueBlockPriority } from "@/components/issues/board-views/block-priority";
import { IssueBlockState } from "@/components/issues/board-views/block-state";
import { useMobxStore } from "@/lib/mobx/store-provider";
import { useMobxStore } from "@/hooks/store";
// components
// interfaces

View File

@ -6,7 +6,7 @@ import { StateGroupIcon } from "@plane/ui";
import { issueGroupFilter } from "@/constants/data";
// ui
// mobx hook
import { useMobxStore } from "@/lib/mobx/store-provider";
import { useMobxStore } from "@/hooks/store";
import { RootStore } from "@/store/root.store";
import { IIssueState } from "types/issue";

View File

@ -9,7 +9,7 @@ import { IssueKanBanHeader } from "@/components/issues/board-views/kanban/header
import { Icon } from "@/components/ui";
// interfaces
// mobx hook
import { useMobxStore } from "@/lib/mobx/store-provider";
import { useMobxStore } from "@/hooks/store";
import { RootStore } from "@/store/root.store";
import { IIssueState, IIssue } from "types/issue";

View File

@ -7,7 +7,7 @@ import { IssueBlockLabels } from "@/components/issues/board-views/block-labels";
import { IssueBlockPriority } from "@/components/issues/board-views/block-priority";
import { IssueBlockState } from "@/components/issues/board-views/block-state";
// mobx hook
import { useMobxStore } from "@/lib/mobx/store-provider";
import { useMobxStore } from "@/hooks/store";
// interfaces
import { RootStore } from "@/store/root.store";
import { IIssue } from "types/issue";

View File

@ -6,7 +6,7 @@ import { StateGroupIcon } from "@plane/ui";
// constants
import { issueGroupFilter } from "@/constants/data";
// mobx hook
import { useMobxStore } from "@/lib/mobx/store-provider";
import { useMobxStore } from "@/hooks/store";
import { RootStore } from "@/store/root.store";
import { IIssueState } from "types/issue";

View File

@ -4,7 +4,7 @@ import { IssueListBlock } from "@/components/issues/board-views/list/block";
import { IssueListHeader } from "@/components/issues/board-views/list/header";
// interfaces
// mobx hook
import { useMobxStore } from "@/lib/mobx/store-provider";
import { useMobxStore } from "@/hooks/store";
// store
import { RootStore } from "@/store/root.store";
import { IIssueState, IIssue } from "types/issue";

View File

@ -3,7 +3,7 @@ import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
// components
// store
import { useMobxStore } from "@/lib/mobx/store-provider";
import { useMobxStore } from "@/hooks/store";
import { IIssueFilterOptions } from "@/store/issues/types";
import { RootStore } from "@/store/root.store";
import { AppliedFiltersList } from "./filters-list";

View File

@ -2,7 +2,7 @@ import { FC, useCallback } from "react";
import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
// components
import { useMobxStore } from "@/lib/mobx/store-provider";
import { useMobxStore } from "@/hooks/store";
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/store/issues/helpers";
import { IIssueFilterOptions } from "@/store/issues/types";
import { RootStore } from "@/store/root.store";

View File

@ -8,8 +8,7 @@ import { Avatar, Button } from "@plane/ui";
import { ProjectLogo } from "@/components/common";
import { IssueFiltersDropdown } from "@/components/issues/filters";
// hooks
import { useUser } from "@/hooks/store";
import { useMobxStore } from "@/lib/mobx/store-provider";
import { useMobxStore, useUser } from "@/hooks/store";
// store
import { RootStore } from "@/store/root.store";
import { TIssueBoardKeys } from "@/types/issue";

View File

@ -3,7 +3,7 @@ import { useRouter } from "next/router";
// constants
import { issueViews } from "@/constants/data";
// mobx
import { useMobxStore } from "@/lib/mobx/store-provider";
import { useMobxStore } from "@/hooks/store";
import { RootStore } from "@/store/root.store";
import { TIssueBoardKeys } from "types/issue";

View File

@ -6,10 +6,8 @@ import { useForm, Controller } from "react-hook-form";
import { EditorRefApi } from "@plane/lite-text-editor";
import { LiteTextEditor } from "@/components/editor/lite-text-editor";
// hooks
import { useUser } from "@/hooks/store";
import { useMobxStore, useUser } from "@/hooks/store";
import useToast from "@/hooks/use-toast";
// lib
import { useMobxStore } from "@/lib/mobx/store-provider";
// types
import { Comment } from "@/types/issue";

View File

@ -10,9 +10,7 @@ import { CommentReactions } from "@/components/issues/peek-overview";
// helpers
import { timeAgo } from "@/helpers/date-time.helper";
// hooks
import { useUser } from "@/hooks/store";
// mobx store
import { useMobxStore } from "@/lib/mobx/store-provider";
import { useMobxStore, useUser } from "@/hooks/store";
// store
import { RootStore } from "@/store/root.store";
// types

View File

@ -1,16 +1,13 @@
import React from "react";
// mobx
import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
// ui
import { Tooltip } from "@plane/ui";
// ui
import { ReactionSelector } from "@/components/ui";
// helpers
import { groupReactions, renderEmoji } from "@/helpers/emoji.helper";
// hooks
import { useUser } from "@/hooks/store";
import { useMobxStore } from "@/lib/mobx/store-provider";
import { useMobxStore, useUser } from "@/hooks/store";
type Props = {
commentId: string;

View File

@ -8,7 +8,7 @@ import { Icon } from "@/components/ui";
// helpers
import { copyTextToClipboard } from "@/helpers/string.helper";
// store
import { useMobxStore } from "@/lib/mobx/store-provider";
import { useMobxStore } from "@/hooks/store";
import { IPeekMode } from "@/store/issue_details";
import { RootStore } from "@/store/root.store";
// lib

View File

@ -2,13 +2,12 @@ import React from "react";
import { observer } from "mobx-react-lite";
import Link from "next/link";
import { useRouter } from "next/router";
// lib
import { Button } from "@plane/ui";
// components
import { CommentCard, AddComment } from "@/components/issues/peek-overview";
import { Icon } from "@/components/ui";
// hooks
import { useUser } from "@/hooks/store";
import { useMobxStore } from "@/lib/mobx/store-provider";
import { useMobxStore, useUser } from "@/hooks/store";
// types
import { IIssue } from "@/types/issue";

View File

@ -4,12 +4,10 @@ import { useRouter } from "next/router";
// lib
import { Tooltip } from "@plane/ui";
import { ReactionSelector } from "@/components/ui";
// helpers
import { groupReactions, renderEmoji } from "@/helpers/emoji.helper";
// hooks
import { useUser } from "@/hooks/store";
import { useMobxStore } from "@/lib/mobx/store-provider";
// helpers
// components
import { useMobxStore, useUser } from "@/hooks/store";
export const IssueEmojiReactions: React.FC = observer(() => {
// router

View File

@ -1,5 +1,5 @@
import { IssueEmojiReactions, IssueVotes } from "@/components/issues/peek-overview";
import { useMobxStore } from "@/lib/mobx/store-provider";
import { useMobxStore } from "@/hooks/store";
export const IssueReactions: React.FC = () => {
const { project: projectStore } = useMobxStore();

View File

@ -1,15 +1,9 @@
import { useState, useEffect } from "react";
import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
// mobx
// lib
import { Tooltip } from "@plane/ui";
// hooks
import { useUser } from "@/hooks/store";
import { useMobxStore } from "@/lib/mobx/store-provider";
// ui
import { useMobxStore, useUser } from "@/hooks/store";
export const IssueVotes: React.FC = observer(() => {
const [isSubmitting, setIsSubmitting] = useState(false);

View File

@ -9,7 +9,7 @@ import { Dialog, Transition } from "@headlessui/react";
// components
import { FullScreenPeekView, SidePeekView } from "@/components/issues/peek-overview";
// lib
import { useMobxStore } from "@/lib/mobx/store-provider";
import { useMobxStore } from "@/hooks/store";
export const IssuePeekOverview: React.FC = observer(() => {
// states

View File

@ -20,14 +20,14 @@ export const AuthView = observer(() => {
const { data: currentUser, fetchCurrentUser, isLoading } = useUser();
// fetching user information
useSWR("CURRENT_USER_DETAILS", () => fetchCurrentUser(), {
const { isLoading: isSWRLoading } = useSWR("CURRENT_USER_DETAILS", () => fetchCurrentUser(), {
shouldRetryOnError: false,
revalidateOnFocus: false,
});
return (
<>
{isLoading ? (
{isLoading || isSWRLoading ? (
<div className="relative flex h-screen w-screen items-center justify-center">
<Spinner />
</div>

View File

@ -11,8 +11,7 @@ import { IssueSpreadsheetView } from "@/components/issues/board-views/spreadshee
import { IssueAppliedFilters } from "@/components/issues/filters/applied-filters/root";
import { IssuePeekOverview } from "@/components/issues/peek-overview";
// mobx store
import { useUser } from "@/hooks/store";
import { useMobxStore } from "@/lib/mobx/store-provider";
import { useMobxStore, useUser } from "@/hooks/store";
import { RootStore } from "@/store/root.store";
// assets
import SomethingWentWrongImage from "public/something-went-wrong.svg";

View File

@ -0,0 +1,291 @@
import { ReactNode } from "react";
import Link from "next/link";
export enum EPageTypes {
"INIT" = "INIT",
"PUBLIC" = "PUBLIC",
"NON_AUTHENTICATED" = "NON_AUTHENTICATED",
"ONBOARDING" = "ONBOARDING",
"AUTHENTICATED" = "AUTHENTICATED",
}
export enum EAuthModes {
SIGN_IN = "SIGN_IN",
SIGN_UP = "SIGN_UP",
}
export enum EAuthSteps {
EMAIL = "EMAIL",
PASSWORD = "PASSWORD",
UNIQUE_CODE = "UNIQUE_CODE",
}
export enum EAuthenticationErrorCodes {
INSTANCE_NOT_CONFIGURED = "5000",
SIGNUP_DISABLED = "5001",
INVALID_PASSWORD = "5002",
USER_ALREADY_EXIST = "5003",
USER_DOES_NOT_EXIST = "5004",
AUTHENTICATION_FAILED_SIGN_IN = "5005",
AUTHENTICATION_FAILED_SIGN_UP = "5006",
SMTP_NOT_CONFIGURED = "5007",
INVALID_MAGIC_CODE = "5008",
EXPIRED_MAGIC_CODE = "5009",
GOOGLE_NOT_CONFIGURED = "5010",
GITHUB_NOT_CONFIGURED = "5011",
INVALID_EMAIL = "5012",
EMAIL_REQUIRED = "5013",
REQUIRED_EMAIL_PASSWORD_SIGN_IN = "5014",
INVALID_EMAIL_SIGN_IN = "5015",
INVALID_EMAIL_SIGN_UP = "5016",
INVALID_EMAIL_MAGIC_SIGN_IN = "5017",
INVALID_EMAIL_MAGIC_SIGN_UP = "5018",
GITHUB_OAUTH_PROVIDER_ERROR = "5019",
GOOGLE_OAUTH_PROVIDER_ERROR = "5020",
MAGIC_SIGN_IN_EMAIL_CODE_REQUIRED = "5021",
MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED = "5022",
INVALID_PASSWORD_TOKEN = "5023",
EXPIRED_PASSWORD_TOKEN = "5024",
INCORRECT_OLD_PASSWORD = "5025",
INVALID_NEW_PASSWORD = "5026",
PASSWORD_ALREADY_SET = "5027",
ADMIN_ALREADY_EXIST = "5028",
REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME = "5029",
INVALID_ADMIN_EMAIL = "5030",
INVALID_ADMIN_PASSWORD = "5031",
REQUIRED_ADMIN_EMAIL_PASSWORD = "5032",
ADMIN_AUTHENTICATION_FAILED = "5034",
}
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 type TAuthErrorInfo = {
type: EErrorAlertType;
code: EAuthenticationErrorCodes;
title: string;
message: ReactNode;
};
const errorCodeMessages: { [key in EAuthenticationErrorCodes]: { title: string; message: ReactNode } } = {
[EAuthenticationErrorCodes.INSTANCE_NOT_CONFIGURED]: {
title: `Instance not configured`,
message: `Instance not configured. Please contact your administrator.`,
},
[EAuthenticationErrorCodes.SIGNUP_DISABLED]: {
title: `Sign up disabled`,
message: `Sign up disabled. Please contact your administrator.`,
},
[EAuthenticationErrorCodes.INVALID_PASSWORD]: {
title: `Invalid password`,
message: `Invalid password. Please try again.`,
},
[EAuthenticationErrorCodes.USER_ALREADY_EXIST]: {
title: `User already exists`,
message: (
<div>
Your account is already registered.&nbsp;
<Link
className="underline underline-offset-4 font-medium hover:font-bold transition-all"
href={`/accounts/sign-in`}
>
Sign In
</Link>
&nbsp;now.
</div>
),
},
[EAuthenticationErrorCodes.USER_DOES_NOT_EXIST]: {
title: `User does not exist`,
message: (
<div>
No account found.&nbsp;
<Link className="underline underline-offset-4 font-medium hover:font-bold transition-all" href={`/`}>
Create one
</Link>
&nbsp;to get started.
</div>
),
},
[EAuthenticationErrorCodes.AUTHENTICATION_FAILED_SIGN_IN]: {
title: `Authentication failed`,
message: `Authentication failed. Please try again.`,
},
[EAuthenticationErrorCodes.AUTHENTICATION_FAILED_SIGN_UP]: {
title: `Authentication failed`,
message: `Authentication failed. Please try again.`,
},
[EAuthenticationErrorCodes.SMTP_NOT_CONFIGURED]: {
title: `SMTP not configured`,
message: `SMTP not configured. Please contact your administrator.`,
},
[EAuthenticationErrorCodes.INVALID_MAGIC_CODE]: {
title: `Authentication failed`,
message: `Invalid magic code. Please try again.`,
},
[EAuthenticationErrorCodes.EXPIRED_MAGIC_CODE]: {
title: `Expired magic code`,
message: `Expired magic code. Please try again.`,
},
[EAuthenticationErrorCodes.GOOGLE_NOT_CONFIGURED]: {
title: `Google not configured`,
message: `Google not configured. Please contact your administrator.`,
},
[EAuthenticationErrorCodes.GITHUB_NOT_CONFIGURED]: {
title: `GitHub not configured`,
message: `GitHub not configured. Please contact your administrator.`,
},
[EAuthenticationErrorCodes.INVALID_EMAIL]: {
title: `Invalid email`,
message: `Invalid email. Please try again.`,
},
[EAuthenticationErrorCodes.EMAIL_REQUIRED]: {
title: `Email required`,
message: `Email required. Please try again.`,
},
[EAuthenticationErrorCodes.REQUIRED_EMAIL_PASSWORD_SIGN_IN]: {
title: `Email and password required`,
message: `Email and password required. Please try again.`,
},
[EAuthenticationErrorCodes.INVALID_EMAIL_SIGN_IN]: {
title: `Invalid email`,
message: `Invalid email. Please try again.`,
},
[EAuthenticationErrorCodes.INVALID_EMAIL_SIGN_UP]: {
title: `Invalid email`,
message: `Invalid email. Please try again.`,
},
[EAuthenticationErrorCodes.INVALID_EMAIL_MAGIC_SIGN_IN]: {
title: `Invalid email`,
message: `Invalid email. Please try again.`,
},
[EAuthenticationErrorCodes.INVALID_EMAIL_MAGIC_SIGN_UP]: {
title: `Invalid email`,
message: `Invalid email. Please try again.`,
},
[EAuthenticationErrorCodes.GITHUB_OAUTH_PROVIDER_ERROR]: {
title: `GitHub OAuth provider error`,
message: `GitHub OAuth provider error. Please try again.`,
},
[EAuthenticationErrorCodes.GOOGLE_OAUTH_PROVIDER_ERROR]: {
title: `Google OAuth provider error`,
message: `Google OAuth provider error. Please try again.`,
},
[EAuthenticationErrorCodes.MAGIC_SIGN_IN_EMAIL_CODE_REQUIRED]: {
title: `Email and code required`,
message: `Email and code required. Please try again.`,
},
[EAuthenticationErrorCodes.MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED]: {
title: `Email and code required`,
message: `Email and code required. Please try again.`,
},
[EAuthenticationErrorCodes.INVALID_PASSWORD_TOKEN]: {
title: `Invalid password token`,
message: `Invalid password token. Please try again.`,
},
[EAuthenticationErrorCodes.EXPIRED_PASSWORD_TOKEN]: {
title: `Expired password token`,
message: `Expired password token. Please try again.`,
},
[EAuthenticationErrorCodes.INCORRECT_OLD_PASSWORD]: {
title: `Incorrect old password`,
message: `Incorrect old password. Please try again.`,
},
[EAuthenticationErrorCodes.INVALID_NEW_PASSWORD]: {
title: `Invalid new password`,
message: `Invalid new password. Please try again.`,
},
[EAuthenticationErrorCodes.PASSWORD_ALREADY_SET]: {
title: `Password already set`,
message: `Password already set. Please try again.`,
},
[EAuthenticationErrorCodes.ADMIN_ALREADY_EXIST]: {
title: `Admin already exists`,
message: `Admin already exists. Please try again.`,
},
[EAuthenticationErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME]: {
title: `Email, password and first name required`,
message: `Email, password and first name required. Please try again.`,
},
[EAuthenticationErrorCodes.INVALID_ADMIN_EMAIL]: {
title: `Invalid email`,
message: `Invalid email. Please try again.`,
},
[EAuthenticationErrorCodes.INVALID_ADMIN_PASSWORD]: {
title: `Invalid password`,
message: `Invalid password. Please try again.`,
},
[EAuthenticationErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD]: {
title: `Email and password required`,
message: `Email and password required. Please try again.`,
},
[EAuthenticationErrorCodes.ADMIN_AUTHENTICATION_FAILED]: {
title: `Authentication failed`,
message: `Authentication failed. Please try again.`,
},
};
export const authErrorHandler = (errorCode: EAuthenticationErrorCodes): TAuthErrorInfo | undefined => {
const toastAlertErrorCodes = [
EAuthenticationErrorCodes.INSTANCE_NOT_CONFIGURED,
EAuthenticationErrorCodes.SIGNUP_DISABLED,
EAuthenticationErrorCodes.INVALID_PASSWORD,
EAuthenticationErrorCodes.AUTHENTICATION_FAILED_SIGN_IN,
EAuthenticationErrorCodes.AUTHENTICATION_FAILED_SIGN_UP,
EAuthenticationErrorCodes.SMTP_NOT_CONFIGURED,
EAuthenticationErrorCodes.INVALID_MAGIC_CODE,
EAuthenticationErrorCodes.EXPIRED_MAGIC_CODE,
EAuthenticationErrorCodes.GOOGLE_NOT_CONFIGURED,
EAuthenticationErrorCodes.GITHUB_NOT_CONFIGURED,
EAuthenticationErrorCodes.INVALID_EMAIL,
EAuthenticationErrorCodes.EMAIL_REQUIRED,
EAuthenticationErrorCodes.REQUIRED_EMAIL_PASSWORD_SIGN_IN,
EAuthenticationErrorCodes.INVALID_EMAIL_SIGN_IN,
EAuthenticationErrorCodes.INVALID_EMAIL_SIGN_UP,
EAuthenticationErrorCodes.INVALID_EMAIL_MAGIC_SIGN_IN,
EAuthenticationErrorCodes.INVALID_EMAIL_MAGIC_SIGN_UP,
EAuthenticationErrorCodes.GITHUB_OAUTH_PROVIDER_ERROR,
EAuthenticationErrorCodes.GOOGLE_OAUTH_PROVIDER_ERROR,
EAuthenticationErrorCodes.MAGIC_SIGN_IN_EMAIL_CODE_REQUIRED,
EAuthenticationErrorCodes.MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED,
EAuthenticationErrorCodes.INVALID_PASSWORD_TOKEN,
EAuthenticationErrorCodes.EXPIRED_PASSWORD_TOKEN,
EAuthenticationErrorCodes.INCORRECT_OLD_PASSWORD,
EAuthenticationErrorCodes.INVALID_NEW_PASSWORD,
EAuthenticationErrorCodes.PASSWORD_ALREADY_SET,
EAuthenticationErrorCodes.ADMIN_ALREADY_EXIST,
EAuthenticationErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME,
EAuthenticationErrorCodes.INVALID_ADMIN_EMAIL,
EAuthenticationErrorCodes.INVALID_ADMIN_PASSWORD,
EAuthenticationErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD,
EAuthenticationErrorCodes.ADMIN_AUTHENTICATION_FAILED,
];
const bannerAlertErrorCodes = [
EAuthenticationErrorCodes.USER_ALREADY_EXIST,
EAuthenticationErrorCodes.USER_DOES_NOT_EXIST,
];
if (toastAlertErrorCodes.includes(errorCode))
return {
type: EErrorAlertType.TOAST_ALERT,
code: errorCode,
title: errorCodeMessages[errorCode]?.title || "Error",
message: errorCodeMessages[errorCode]?.message || "Something went wrong. Please try again.",
};
if (bannerAlertErrorCodes.includes(errorCode))
return {
type: EErrorAlertType.BANNER_ALERT,
code: errorCode,
title: errorCodeMessages[errorCode]?.title || "Error",
message: errorCodeMessages[errorCode]?.message || "Something went wrong. Please try again.",
};
return undefined;
};

View File

@ -1,2 +1,4 @@
export * from "./user-mobx-provider";
export * from "./use-instance";
export * from "./user";

View File

@ -0,0 +1,10 @@
import { useContext } from "react";
// store
import { StoreContext } from "@/lib/store-context";
import { RootStore } from "@/store/root.store";
export const useMobxStore = (): RootStore => {
const context = useContext(StoreContext);
if (context === undefined) throw new Error("useMobxStore must be used within StoreProvider");
return context;
};

View File

@ -1,4 +1,4 @@
import { useMobxStore } from "@/lib/mobx/store-provider";
import { useMobxStore } from "@/hooks/store";
import { RootStore } from "@/store/root.store";
const useEditorSuggestions = () => {

View File

@ -1,28 +0,0 @@
"use client";
import { createContext, useContext } from "react";
// mobx store
import { RootStore } from "@/store/root.store";
let rootStore: RootStore = new RootStore();
export const MobxStoreContext = createContext<RootStore>(rootStore);
const initializeStore = () => {
const singletonRootStore: RootStore = rootStore ?? new RootStore();
if (typeof window === "undefined") return singletonRootStore;
if (!rootStore) rootStore = singletonRootStore;
return singletonRootStore;
};
export const MobxStoreProvider = ({ children }: any) => {
const store: RootStore = initializeStore();
return <MobxStoreContext.Provider value={store}>{children}</MobxStoreContext.Provider>;
};
// hook
export const useMobxStore = () => {
const context = useContext(MobxStoreContext);
if (context === undefined) throw new Error("useMobxStore must be used within MobxStoreProvider");
return context;
};

View File

@ -0,0 +1,86 @@
import { FC, ReactNode } from "react";
import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
import useSWR from "swr";
import { Spinner } from "@plane/ui";
// helpers
import { EPageTypes } from "@/helpers/authentication.helper";
// hooks
import { useUser, useUserProfile } from "@/hooks/store";
type TAuthWrapper = {
children: ReactNode;
pageType?: EPageTypes;
};
export const AuthWrapper: FC<TAuthWrapper> = observer((props) => {
const router = useRouter();
const { children, pageType = EPageTypes.AUTHENTICATED } = props;
// hooks
const { isLoading, data: currentUser, fetchCurrentUser } = useUser();
const { data: currentUserProfile } = useUserProfile();
console;
const { isLoading: isSWRLoading } = useSWR("INSTANCE_INFORMATION", () => fetchCurrentUser(), {
revalidateOnFocus: false,
});
if (isSWRLoading || isLoading)
return (
<div className="relative flex h-screen w-full items-center justify-center">
<Spinner />
</div>
);
if (pageType === EPageTypes.PUBLIC) return <>{children}</>;
if (pageType === EPageTypes.INIT) {
if (!currentUser?.id) return <>{children}</>;
else {
if (currentUserProfile?.id && currentUserProfile?.onboarding_step?.profile_complete) return <>{children}</>;
else {
router.push(`/onboarding`);
return <></>;
}
}
}
if (pageType === EPageTypes.NON_AUTHENTICATED) {
if (!currentUser?.id) return <>{children}</>;
else {
if (currentUserProfile?.id && currentUserProfile?.onboarding_step?.profile_complete) {
router.push(`/`);
return <></>;
} else {
router.push(`/onboarding`);
return <></>;
}
}
}
if (pageType === EPageTypes.ONBOARDING) {
if (!currentUser?.id) {
router.push(`/`);
return <></>;
} else {
if (currentUserProfile?.id && currentUserProfile?.onboarding_step?.profile_complete) {
router.push(`/`);
return <></>;
} else return <>{children}</>;
}
}
if (pageType === EPageTypes.AUTHENTICATED) {
if (!currentUser?.id) return <>{children}</>;
else {
if (currentUserProfile?.id && currentUserProfile?.onboarding_step?.profile_complete) return <>{children}</>;
else {
router.push(`/onboarding`);
return <></>;
}
}
}
return <>{children}</>;
});

View File

@ -0,0 +1,2 @@
export * from "./instance-wrapper";
export * from "./auth-wrapper";

View File

@ -8,20 +8,20 @@ import { InstanceNotReady } from "@/components/instance";
// hooks
import { useInstance } from "@/hooks/store";
type TInstanceLayout = {
type TInstanceWrapper = {
children: ReactNode;
};
const InstanceLayout: FC<TInstanceLayout> = observer((props) => {
export const InstanceWrapper: FC<TInstanceWrapper> = observer((props) => {
const { children } = props;
// store
// hooks
const { isLoading, instance, fetchInstanceInfo } = useInstance();
useSWR("INSTANCE_INFORMATION", () => fetchInstanceInfo(), {
const { isLoading: isSWRLoading } = useSWR("INSTANCE_INFORMATION", () => fetchInstanceInfo(), {
revalidateOnFocus: false,
});
if (isLoading)
if (isSWRLoading || isLoading)
return (
<div className="relative flex h-screen w-full items-center justify-center">
<Spinner />
@ -32,5 +32,3 @@ const InstanceLayout: FC<TInstanceLayout> = observer((props) => {
return <>{children}</>;
});
export default InstanceLayout;

View File

@ -1,14 +1,16 @@
import Head from "next/head";
import { useRouter } from "next/router";
import useSWR from "swr";
/// layouts
// components
import { ProjectDetailsView } from "@/components/views";
// lib
import { useMobxStore } from "@/lib/mobx/store-provider";
import ProjectLayout from "layouts/project-layout";
// helpers
import { EPageTypes } from "@/helpers/authentication.helper";
// hooks
import { useMobxStore } from "@/hooks/store";
// layouts
import ProjectLayout from "@/layouts/project-layout";
// wrappers
import { AuthWrapper } from "@/lib/wrappers";
const WorkspaceProjectPage = (props: any) => {
const SITE_TITLE = props?.project_settings?.project_details?.name || "Plane | Deploy";
@ -31,12 +33,14 @@ const WorkspaceProjectPage = (props: any) => {
});
return (
<ProjectLayout>
<Head>
<title>{SITE_TITLE}</title>
</Head>
<ProjectDetailsView />
</ProjectLayout>
<AuthWrapper pageType={EPageTypes.AUTHENTICATED}>
<ProjectLayout>
<Head>
<title>{SITE_TITLE}</title>
</Head>
<ProjectDetailsView />
</ProjectLayout>
</AuthWrapper>
);
};

View File

@ -7,15 +7,15 @@ import "@/styles/globals.css";
import { SITE_NAME, SITE_DESCRIPTION, SITE_URL, TWITTER_USER_NAME, SITE_KEYWORDS, SITE_TITLE } from "@/constants/seo";
import { ToastContextProvider } from "@/contexts/toast.context";
// mobx store provider
import InstanceLayout from "@/layouts/instance-layout";
import { MobxStoreProvider } from "@/lib/mobx/store-provider";
// constants
import { StoreProvider } from "@/lib/store-context";
// wrappers
import { InstanceWrapper } from "@/lib/wrappers";
const prefix = parseInt(process.env.NEXT_PUBLIC_DEPLOY_WITH_NGINX || "0") === 0 ? "/" : "/spaces/";
function MyApp({ Component, pageProps }: AppProps) {
return (
<MobxStoreProvider>
<>
<Head>
<title>{SITE_TITLE}</title>
<meta property="og:site_name" content={SITE_NAME} />
@ -31,14 +31,16 @@ function MyApp({ Component, pageProps }: AppProps) {
<link rel="manifest" href={`${prefix}site.webmanifest.json`} />
<link rel="shortcut icon" href={`${prefix}favicon/favicon.ico`} />
</Head>
<ToastContextProvider>
<ThemeProvider themes={["light", "dark"]} defaultTheme="system" enableSystem>
<InstanceLayout>
<Component {...pageProps} />
</InstanceLayout>
</ThemeProvider>
</ToastContextProvider>
</MobxStoreProvider>
<StoreProvider>
<ToastContextProvider>
<ThemeProvider themes={["light", "dark"]} defaultTheme="system" enableSystem>
<InstanceWrapper>
<Component {...pageProps} />
</InstanceWrapper>
</ThemeProvider>
</ToastContextProvider>
</StoreProvider>
</>
);
}

View File

@ -9,11 +9,13 @@ import { CircleCheck } from "lucide-react";
// ui
import { Button, Input, TOAST_TYPE, getButtonStyling, setToast } from "@plane/ui";
// helpers
import { EPageTypes } from "@/helpers/authentication.helper";
import { cn } from "@/helpers/common.helper";
import { checkEmailValidity } from "@/helpers/string.helper";
// hooks
// import useAuthRedirection from "@/hooks/use-auth-redirection";
import useTimer from "@/hooks/use-timer";
// wrappers
import { AuthWrapper } from "@/lib/wrappers";
// services
import { AuthService } from "@/services/authentication.service";
// images
@ -77,85 +79,87 @@ const ForgotPasswordPage: NextPage = () => {
};
return (
<div className="relative h-screen w-full overflow-hidden">
<div className="absolute inset-0 z-0">
<Image
src={resolvedTheme === "dark" ? PlaneBackgroundPatternDark : PlaneBackgroundPattern}
className="w-full h-full object-cover"
alt="Plane background pattern"
/>
</div>
<div className="relative z-10">
<div className="flex items-center justify-between px-8 pb-4 sm:px-16 sm:py-5 lg:px-28">
<div className="flex items-center gap-x-2 py-10">
<Image src={BluePlaneLogoWithoutText} height={30} width={30} alt="Plane Logo" className="mr-2" />
<span className="text-2xl font-semibold sm:text-3xl">Plane</span>
</div>
<AuthWrapper pageType={EPageTypes.NON_AUTHENTICATED}>
<div className="relative h-screen w-full overflow-hidden">
<div className="absolute inset-0 z-0">
<Image
src={resolvedTheme === "dark" ? PlaneBackgroundPatternDark : PlaneBackgroundPattern}
className="w-full h-full object-cover"
alt="Plane background pattern"
/>
</div>
<div className="mx-auto h-full">
<div className="h-full overflow-auto px-7 pb-56 pt-4 sm:px-0">
<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">
Reset your password
</h3>
<p className="font-medium text-onboarding-text-400">
Enter your user account{"'"}s verified email address and we will send you a password reset link.
</p>
</div>
<form onSubmit={handleSubmit(handleForgotPassword)} className="mx-auto mt-5 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, ref } }) => (
<Input
id="email"
name="email"
type="email"
value={value}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.email)}
placeholder="name@company.com"
className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
disabled={resendTimerCode > 0}
/>
)}
/>
{resendTimerCode > 0 && (
<p className="flex w-full items-start px-1 gap-1 text-xs font-medium text-green-700">
<CircleCheck height={12} width={12} className="mt-0.5" />
We sent the reset link to your email address
</p>
)}
<div className="relative z-10">
<div className="flex items-center justify-between px-8 pb-4 sm:px-16 sm:py-5 lg:px-28">
<div className="flex items-center gap-x-2 py-10">
<Image src={BluePlaneLogoWithoutText} height={30} width={30} alt="Plane Logo" className="mr-2" />
<span className="text-2xl font-semibold sm:text-3xl">Plane</span>
</div>
</div>
<div className="mx-auto h-full">
<div className="h-full overflow-auto px-7 pb-56 pt-4 sm:px-0">
<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">
Reset your password
</h3>
<p className="font-medium text-onboarding-text-400">
Enter your user account{"'"}s verified email address and we will send you a password reset link.
</p>
</div>
<Button
type="submit"
variant="primary"
className="w-full"
size="lg"
disabled={!isValid}
loading={isSubmitting || resendTimerCode > 0}
>
{resendTimerCode > 0 ? `Resend in ${resendTimerCode} seconds` : "Send reset link"}
</Button>
<Link href="/" className={cn("w-full", getButtonStyling("link-neutral", "lg"))}>
Back to sign in
</Link>
</form>
<form onSubmit={handleSubmit(handleForgotPassword)} className="mx-auto mt-5 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, ref } }) => (
<Input
id="email"
name="email"
type="email"
value={value}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.email)}
placeholder="name@company.com"
className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
disabled={resendTimerCode > 0}
/>
)}
/>
{resendTimerCode > 0 && (
<p className="flex w-full items-start px-1 gap-1 text-xs font-medium text-green-700">
<CircleCheck height={12} width={12} className="mt-0.5" />
We sent the reset link to your email address
</p>
)}
</div>
<Button
type="submit"
variant="primary"
className="w-full"
size="lg"
disabled={!isValid}
loading={isSubmitting || resendTimerCode > 0}
>
{resendTimerCode > 0 ? `Resend in ${resendTimerCode} seconds` : "Send reset link"}
</Button>
<Link href="/" className={cn("w-full", getButtonStyling("link-neutral", "lg"))}>
Back to sign in
</Link>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</AuthWrapper>
);
};

View File

@ -10,9 +10,11 @@ import { Button, Input } from "@plane/ui";
// components
import { PasswordStrengthMeter } from "@/components/accounts";
// helpers
import { EPageTypes } from "@/helpers/authentication.helper";
import { API_BASE_URL } from "@/helpers/common.helper";
import { getPasswordStrength } from "@/helpers/password.helper";
// hooks
// wrappers
import { AuthWrapper } from "@/lib/wrappers";
// services
import { AuthService } from "@/services/authentication.service";
// images
@ -75,98 +77,71 @@ const ResetPasswordPage: NextPage = () => {
);
return (
<div className="relative h-screen w-full overflow-hidden">
<div className="absolute inset-0 z-0">
<Image
src={resolvedTheme === "dark" ? PlaneBackgroundPatternDark : PlaneBackgroundPattern}
className="w-full h-full object-cover"
alt="Plane background pattern"
/>
</div>
<div className="relative z-10">
<div className="flex items-center justify-between px-8 pb-4 sm:px-16 sm:py-5 lg:px-28">
<div className="flex items-center gap-x-2 py-10">
<Image src={BluePlaneLogoWithoutText} height={30} width={30} alt="Plane Logo" className="mr-2" />
<span className="text-2xl font-semibold sm:text-3xl">Plane</span>
</div>
<AuthWrapper pageType={EPageTypes.NON_AUTHENTICATED}>
<div className="relative h-screen w-full overflow-hidden">
<div className="absolute inset-0 z-0">
<Image
src={resolvedTheme === "dark" ? PlaneBackgroundPatternDark : PlaneBackgroundPattern}
className="w-full h-full object-cover"
alt="Plane background pattern"
/>
</div>
<div className="mx-auto h-full">
<div className="h-full overflow-auto px-7 pb-56 pt-4 sm:px-0">
<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">
Set new password
</h3>
<p className="font-medium text-onboarding-text-400">Secure your account with a strong password</p>
</div>
<form
className="mx-auto mt-5 space-y-4 w-5/6 sm:w-96"
method="POST"
action={`${API_BASE_URL}/auth/reset-password/${uidb64?.toString()}/${token?.toString()}/`}
>
<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={resetFormData.email}
//hasError={Boolean(errors.email)}
placeholder="name@company.com"
className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 text-onboarding-text-400 cursor-not-allowed"
disabled
/>
</div>
<div className="relative z-10">
<div className="flex items-center justify-between px-8 pb-4 sm:px-16 sm:py-5 lg:px-28">
<div className="flex items-center gap-x-2 py-10">
<Image src={BluePlaneLogoWithoutText} height={30} width={30} alt="Plane Logo" className="mr-2" />
<span className="text-2xl font-semibold sm:text-3xl">Plane</span>
</div>
</div>
<div className="mx-auto h-full">
<div className="h-full overflow-auto px-7 pb-56 pt-4 sm:px-0">
<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">
Set new password
</h3>
<p className="font-medium text-onboarding-text-400">Secure your account with a strong password</p>
</div>
<div className="space-y-1">
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="password">
Password
</label>
<div className="relative flex items-center rounded-md bg-onboarding-background-200">
<Input
type={showPassword ? "text" : "password"}
name="password"
value={resetFormData.password}
onChange={(e) => handleFormChange("password", e.target.value)}
//hasError={Boolean(errors.password)}
placeholder="Enter password"
className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
minLength={8}
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>
{isPasswordInputFocused && <PasswordStrengthMeter password={resetFormData.password} />}
</div>
{getPasswordStrength(resetFormData.password) >= 3 && (
<form
className="mx-auto mt-5 space-y-4 w-5/6 sm:w-96"
method="POST"
action={`${API_BASE_URL}/auth/reset-password/${uidb64?.toString()}/${token?.toString()}/`}
>
<input type="hidden" name="csrfmiddlewaretoken" value={csrfToken} />
<div className="space-y-1">
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="confirm_password">
Confirm password
<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={resetFormData.email}
//hasError={Boolean(errors.email)}
placeholder="name@company.com"
className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 text-onboarding-text-400 cursor-not-allowed"
disabled
/>
</div>
</div>
<div className="space-y-1">
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="password">
Password
</label>
<div className="relative flex items-center rounded-md bg-onboarding-background-200">
<Input
type={showPassword ? "text" : "password"}
name="confirm_password"
value={resetFormData.confirm_password}
onChange={(e) => handleFormChange("confirm_password", e.target.value)}
placeholder="Confirm password"
name="password"
value={resetFormData.password}
onChange={(e) => handleFormChange("password", e.target.value)}
//hasError={Boolean(errors.password)}
placeholder="Enter password"
className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
minLength={8}
onFocus={() => setIsPasswordInputFocused(true)}
onBlur={() => setIsPasswordInputFocused(false)}
autoFocus
/>
{showPassword ? (
<EyeOff
@ -180,20 +155,50 @@ const ResetPasswordPage: NextPage = () => {
/>
)}
</div>
{!!resetFormData.confirm_password && resetFormData.password !== resetFormData.confirm_password && (
<span className="text-sm text-red-500">Passwords don{"'"}t match</span>
)}
{isPasswordInputFocused && <PasswordStrengthMeter password={resetFormData.password} />}
</div>
)}
<Button type="submit" variant="primary" className="w-full" size="lg" disabled={isButtonDisabled}>
Set password
</Button>
</form>
{getPasswordStrength(resetFormData.password) >= 3 && (
<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={resetFormData.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>
{!!resetFormData.confirm_password &&
resetFormData.password !== resetFormData.confirm_password && (
<span className="text-sm text-red-500">Passwords don{"'"}t match</span>
)}
</div>
)}
<Button type="submit" variant="primary" className="w-full" size="lg" disabled={isButtonDisabled}>
Set password
</Button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</AuthWrapper>
);
};

View File

@ -2,8 +2,15 @@ import { observer } from "mobx-react-lite";
import { NextPage } from "next";
// components
import { AuthView } from "@/components/views";
// store
// helpers
import { EPageTypes } from "@/helpers/authentication.helper";
// wrapper
import { AuthWrapper } from "@/lib/wrappers";
const Index: NextPage = observer(() => <AuthView />);
const Index: NextPage = observer(() => (
<AuthWrapper pageType={EPageTypes.INIT}>
<AuthView />
</AuthWrapper>
));
export default Index;

View File

@ -7,8 +7,12 @@ import { useTheme } from "next-themes";
import { Avatar } from "@plane/ui";
// components
import { OnBoardingForm } from "@/components/accounts/onboarding-form";
// mobx
// helpers
import { EPageTypes } from "@/helpers/authentication.helper";
// hooks
import { useUser, useUserProfile } from "@/hooks/store";
// wrappers
import { AuthWrapper } from "@/lib/wrappers";
// assets
import ProfileSetupDark from "public/onboarding/profile-setup-dark.svg";
import ProfileSetup from "public/onboarding/profile-setup.svg";
@ -49,71 +53,78 @@ const OnBoardingPage = observer(() => {
};
return (
<div className="flex h-full w-full">
<div className="w-full h-full overflow-auto px-6 py-10 sm:px-7 sm:py-14 md:px-14 lg:px-28">
<div className="flex items-center justify-between">
<div className="flex w-full items-center justify-between font-semibold ">
<div className="flex items-center gap-x-2">
<Image src={`${imagePrefix}/plane-logos/blue-without-text.png`} height={30} width={30} alt="Plane Logo" />
<AuthWrapper pageType={EPageTypes.ONBOARDING}>
<div className="flex h-full w-full">
<div className="w-full h-full overflow-auto px-6 py-10 sm:px-7 sm:py-14 md:px-14 lg:px-28">
<div className="flex items-center justify-between">
<div className="flex w-full items-center justify-between font-semibold ">
<div className="flex items-center gap-x-2">
<Image
src={`${imagePrefix}/plane-logos/blue-without-text.png`}
height={30}
width={30}
alt="Plane Logo"
/>
</div>
</div>
</div>
<div className="shrink-0 lg:hidden">
<div className="flex w-full shrink-0 justify-end">
<div className="flex items-center gap-x-2 pr-4">
{user?.avatar && (
<Avatar
name={user?.first_name ? `${user?.first_name} ${user?.last_name ?? ""}` : user?.email}
src={user?.avatar}
size={24}
shape="square"
fallbackBackgroundColor="#FCBE1D"
className="!text-base capitalize"
/>
)}
<span className="text-sm font-medium text-custom-text-200">
{user?.first_name ? `${user?.first_name} ${user?.last_name ?? ""}` : user?.email}
</span>
<div className="shrink-0 lg:hidden">
<div className="flex w-full shrink-0 justify-end">
<div className="flex items-center gap-x-2 pr-4">
{user?.avatar && (
<Avatar
name={user?.first_name ? `${user?.first_name} ${user?.last_name ?? ""}` : user?.email}
src={user?.avatar}
size={24}
shape="square"
fallbackBackgroundColor="#FCBE1D"
className="!text-base capitalize"
/>
)}
<span className="text-sm font-medium text-custom-text-200">
{user?.first_name ? `${user?.first_name} ${user?.last_name ?? ""}` : user?.email}
</span>
</div>
</div>
</div>
</div>
</div>
<div className="flex flex-col w-full items-center justify-center p-8 mt-14">
<div className="text-center space-y-1 py-4 mx-auto">
<h3 className="text-3xl font-bold text-onboarding-text-100">Welcome to Plane!</h3>
<p className="font-medium text-onboarding-text-400">
Lets setup your profile, tell us a bit about yourself.
</p>
</div>
<OnBoardingForm user={user} finishOnboarding={finishOnboarding} />
</div>
</div>
<div className="hidden lg:block relative w-2/5 h-screen overflow-hidden px-6 py-10 sm:px-7 sm:py-14 md:px-14 lg:px-28">
<div className="flex w-full shrink-0 justify-end">
<div className="flex items-center gap-x-2 pr-4 z-10">
{user?.avatar && (
<Avatar
name={user?.first_name ? `${user?.first_name} ${user?.last_name ?? ""}` : user?.email}
src={user?.avatar}
size={24}
shape="square"
fallbackBackgroundColor="#FCBE1D"
className="!text-base capitalize"
/>
)}
<span className="text-sm font-medium text-custom-text-200">
{user?.first_name ? `${user?.first_name} ${user?.last_name ?? ""}` : user?.email}
</span>
<div className="flex flex-col w-full items-center justify-center p-8 mt-14">
<div className="text-center space-y-1 py-4 mx-auto">
<h3 className="text-3xl font-bold text-onboarding-text-100">Welcome to Plane!</h3>
<p className="font-medium text-onboarding-text-400">
Lets setup your profile, tell us a bit about yourself.
</p>
</div>
<OnBoardingForm user={user} finishOnboarding={finishOnboarding} />
</div>
</div>
<div className="absolute inset-0 z-0">
<Image
src={resolvedTheme === "dark" ? ProfileSetupDark : ProfileSetup}
className="h-screen w-auto float-end object-cover"
alt="Profile setup"
/>
<div className="hidden lg:block relative w-2/5 h-screen overflow-hidden px-6 py-10 sm:px-7 sm:py-14 md:px-14 lg:px-28">
<div className="flex w-full shrink-0 justify-end">
<div className="flex items-center gap-x-2 pr-4 z-10">
{user?.avatar && (
<Avatar
name={user?.first_name ? `${user?.first_name} ${user?.last_name ?? ""}` : user?.email}
src={user?.avatar}
size={24}
shape="square"
fallbackBackgroundColor="#FCBE1D"
className="!text-base capitalize"
/>
)}
<span className="text-sm font-medium text-custom-text-200">
{user?.first_name ? `${user?.first_name} ${user?.last_name ?? ""}` : user?.email}
</span>
</div>
</div>
<div className="absolute inset-0 z-0">
<Image
src={resolvedTheme === "dark" ? ProfileSetupDark : ProfileSetup}
className="h-screen w-auto float-end object-cover"
alt="Profile setup"
/>
</div>
</div>
</div>
</div>
</AuthWrapper>
);
});

View File

@ -1,32 +1,39 @@
// next imports
import Image from "next/image";
import projectNotPublishedImage from "public/project-not-published.svg";
// helpers
import { EPageTypes } from "@/helpers/authentication.helper";
// wrappers
import { AuthWrapper } from "@/lib/wrappers";
// images
import projectNotPublishedImage from "@/public/project-not-published.svg";
const CustomProjectNotPublishedError = () => (
<div className="relative flex h-full min-h-screen w-screen items-center justify-center py-5">
<div className="max-w-[700px] space-y-5">
<div className="flex flex-col items-center gap-3 text-center">
<div className="relative h-[240px] w-[240px]">
<Image src={projectNotPublishedImage} layout="fill" alt="404- Page not found" />
<AuthWrapper pageType={EPageTypes.PUBLIC}>
<div className="relative flex h-full min-h-screen w-screen items-center justify-center py-5">
<div className="max-w-[700px] space-y-5">
<div className="flex flex-col items-center gap-3 text-center">
<div className="relative h-[240px] w-[240px]">
<Image src={projectNotPublishedImage} layout="fill" alt="404- Page not found" />
</div>
<div className="text-xl font-medium">
Oops! The page you{`'`}re looking for isn{`'`}t live at the moment.
</div>
<div className="text-sm text-custom-text-200">
If this is your project, login to your workspace to adjust its visibility settings and make it public.
</div>
</div>
<div className="text-xl font-medium">
Oops! The page you{`'`}re looking for isn{`'`}t live at the moment.
</div>
<div className="text-sm text-custom-text-200">
If this is your project, login to your workspace to adjust its visibility settings and make it public.
</div>
</div>
<div className="flex items-center justify-center text-center">
<a
href={`https://app.plane.so/`}
className="cursor-pointer select-none rounded-sm border border-gray-200 bg-gray-50 p-1.5 px-2.5 text-sm font-medium text-gray-700 transition-all hover:scale-105 hover:bg-gray-100 hover:text-gray-800"
>
Go to your Workspace
</a>
<div className="flex items-center justify-center text-center">
<a
href={`https://app.plane.so/`}
className="cursor-pointer select-none rounded-sm border border-gray-200 bg-gray-50 p-1.5 px-2.5 text-sm font-medium text-gray-700 transition-all hover:scale-105 hover:bg-gray-100 hover:text-gray-800"
>
Go to your Workspace
</a>
</div>
</div>
</div>
</div>
</AuthWrapper>
);
export default CustomProjectNotPublishedError;

View File

@ -19,7 +19,7 @@ abstract class APIService {
this.axiosInstance.interceptors.response.use(
(response) => response,
(error) => {
if (error.response && error.response.status === 401) window.location.href = "/space/login";
if (error.response && error.response.status === 401) window.location.href = "/";
return Promise.reject(error.response?.data ?? error);
}
);

View File

@ -26,7 +26,7 @@ export class AuthService extends APIService {
});
}
async sendResetPasswordLink(data: { email: string }): Promise<any> {
async sendResetPasswordLink(data: { email: string }): Promise<void> {
return this.post(`/auth/forgot-password/`, data)
.then((response) => response?.data)
.catch((error) => {
@ -34,7 +34,7 @@ export class AuthService extends APIService {
});
}
async generateUniqueCode(data: { email: string }): Promise<any> {
async generateUniqueCode(data: { email: string }): Promise<void> {
return this.post("/auth/spaces/magic-generate/", data, { headers: {} })
.then((response) => response?.data)
.catch((error) => {

View File

@ -18,7 +18,7 @@ export class UserService extends APIService {
});
}
async updateUser(data: Partial<IUser>): Promise<any> {
async updateUser(data: Partial<IUser>): Promise<IUser> {
return this.patch("/api/users/me/", data)
.then((response) => response?.data)
.catch((error) => {
@ -33,7 +33,7 @@ export class UserService extends APIService {
throw error?.response;
});
}
async updateCurrentUserProfile(data: any): Promise<any> {
async updateCurrentUserProfile(data: Partial<TUserProfile>): Promise<TUserProfile> {
return this.patch("/api/users/me/profile/", data)
.then((response) => response?.data)
.catch((error) => {

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react";
import React, { FC, useEffect, useState } from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
import { IEmailCheckData } from "@plane/types";
@ -29,26 +29,42 @@ import { AuthService } from "@/services/auth.service";
const authService = new AuthService();
export const SignInAuthRoot = observer(() => {
type TAuthRoot = {
authMode: EAuthModes;
};
export const AuthRoot: FC<TAuthRoot> = observer((props) => {
//router
const router = useRouter();
const { email: emailParam, invitation_id, slug: workspaceSlug, error_code, error_message } = router.query;
const { email: emailParam, invitation_id, slug: workspaceSlug, error_code } = router.query;
// props
const { authMode } = props;
// states
const [authStep, setAuthStep] = useState<EAuthSteps>(EAuthSteps.EMAIL);
const [email, setEmail] = useState(emailParam ? emailParam.toString() : "");
const [errorInfo, setErrorInfo] = useState<TAuthErrorInfo | undefined>(undefined);
// hooks
const { instance } = useInstance();
// derived values
const authMode = EAuthModes.SIGN_IN;
useEffect(() => {
if (error_code && error_message) {
const errorhandler = authErrorHandler(
error_code?.toString() as EAuthenticationErrorCodes,
error_message?.toString()
);
if (error_code) {
const errorhandler = authErrorHandler(error_code?.toString() as EAuthenticationErrorCodes);
if (errorhandler) {
if (
[
EAuthenticationErrorCodes.AUTHENTICATION_FAILED_SIGN_IN,
EAuthenticationErrorCodes.AUTHENTICATION_FAILED_SIGN_UP,
].includes(errorhandler.code)
)
setAuthStep(EAuthSteps.PASSWORD);
if (
[EAuthenticationErrorCodes.INVALID_MAGIC_CODE, EAuthenticationErrorCodes.EXPIRED_MAGIC_CODE].includes(
errorhandler.code
)
)
setAuthStep(EAuthSteps.UNIQUE_CODE);
// validating wheather to show alert to banner
if (errorhandler?.type === EErrorAlertType.TOAST_ALERT) {
setToast({
type: TOAST_TYPE.ERROR,
@ -58,23 +74,30 @@ export const SignInAuthRoot = observer(() => {
} else setErrorInfo(errorhandler);
}
}
}, [error_code, error_message]);
}, [error_code, authMode]);
// step 1 submit handler- email verification
const handleEmailVerification = async (data: IEmailCheckData) => {
setEmail(data.email);
await authService
.signInEmailCheck(data)
const emailCheckRequest =
authMode === EAuthModes.SIGN_IN ? authService.signInEmailCheck(data) : authService.signUpEmailCheck(data);
await emailCheckRequest
.then(() => {
setAuthStep(EAuthSteps.PASSWORD);
})
.catch((err) => {
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: err?.error_message ?? "Something went wrong. Please try again.",
});
.catch((error) => {
const errorhandler = authErrorHandler(error?.error_code.toString());
if (errorhandler?.type === EErrorAlertType.BANNER_ALERT) {
setErrorInfo(errorhandler);
return;
} else if (errorhandler?.type === EErrorAlertType.TOAST_ALERT)
setToast({
type: TOAST_TYPE.ERROR,
title: errorhandler?.title,
message: (errorhandler?.message as string) || "Something went wrong. Please try again.",
});
});
};
@ -87,7 +110,7 @@ export const SignInAuthRoot = observer(() => {
workspaceSlug={workspaceSlug?.toString() || undefined}
invitationId={invitation_id?.toString() || undefined}
invitationEmail={email || undefined}
authMode={EAuthModes.SIGN_IN}
authMode={authMode}
currentAuthStep={authStep}
>
{errorInfo && errorInfo?.type === EErrorAlertType.BANNER_ALERT && (

View File

@ -1,5 +1,4 @@
export * from "./sign-up-root";
export * from "./sign-in-root";
export * from "./auth-root";
export * from "./auth-header";
export * from "./auth-banner";

View File

@ -1,133 +0,0 @@
import { FC, useEffect, useState } from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
// types
import { IEmailCheckData } from "@plane/types";
// ui
import { TOAST_TYPE, setToast } from "@plane/ui";
// components
import {
AuthHeader,
AuthBanner,
AuthEmailForm,
AuthUniqueCodeForm,
AuthPasswordForm,
OAuthOptions,
TermsAndConditions,
} from "@/components/account";
// 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";
// service initialization
const authService = new AuthService();
export const SignUpAuthRoot: FC = observer(() => {
//router
const router = useRouter();
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 [errorInfo, setErrorInfo] = useState<TAuthErrorInfo | undefined>(undefined);
// hooks
const { instance } = useInstance();
// derived values
const authMode = EAuthModes.SIGN_UP;
const isSmtpConfigured = instance?.config?.is_smtp_configured;
useEffect(() => {
if (error_code && error_message) {
const errorhandler = authErrorHandler(
error_code?.toString() as EAuthenticationErrorCodes,
error_message?.toString()
);
if (errorhandler) {
if (errorhandler?.type === EErrorAlertType.TOAST_ALERT) {
setToast({
type: TOAST_TYPE.ERROR,
title: errorhandler?.title,
message: errorhandler?.message as string,
});
} else setErrorInfo(errorhandler);
}
}
}, [error_code, error_message]);
// 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((error) => {
const errorhandler = authErrorHandler(error?.error_code, error?.error_message);
if (errorhandler) {
setErrorInfo(errorhandler);
return;
}
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: error?.error_message ?? "Something went wrong. Please try again.",
});
});
};
const isOAuthEnabled =
instance?.config && (instance?.config?.is_google_enabled || instance?.config?.is_github_enabled);
return (
<div className="relative 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}
>
{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 && (
<AuthUniqueCodeForm
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} />
</AuthHeader>
</div>
);
});

View File

@ -1,149 +0,0 @@
import { ReactNode } from "react";
export enum EPageTypes {
"PUBLIC" = "PUBLIC",
"NON_AUTHENTICATED" = "NON_AUTHENTICATED",
"ONBOARDING" = "ONBOARDING",
"AUTHENTICATED" = "AUTHENTICATED",
}
export enum EAuthModes {
SIGN_IN = "SIGN_IN",
SIGN_UP = "SIGN_UP",
}
export enum EAuthSteps {
EMAIL = "EMAIL",
PASSWORD = "PASSWORD",
UNIQUE_CODE = "UNIQUE_CODE",
}
export enum EAuthenticationErrorCodes {
// alert errors
INSTANCE_NOT_CONFIGURED = "INSTANCE_NOT_CONFIGURED",
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",
// inline errors from backend
REQUIRED_EMAIL_PASSWORD_FIRST_NAME = "REQUIRED_EMAIL_PASSWORD_FIRST_NAME",
REQUIRED_EMAIL_PASSWORD = "REQUIRED_EMAIL_PASSWORD",
EMAIL_CODE_REQUIRED = "EMAIL_CODE_REQUIRED",
}
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 type TAuthErrorInfo = { type: EErrorAlertType; title: string; message: ReactNode };
const errorCodeMessages: { [key in EAuthenticationErrorCodes]: { title: string; message: ReactNode } } = {
[EAuthenticationErrorCodes.INSTANCE_NOT_CONFIGURED]: {
title: `Instance not configured`,
message: `Instance not configured. Please contact your administrator.`,
},
[EAuthenticationErrorCodes.SMTP_NOT_CONFIGURED]: {
title: `SMTP not configured`,
message: `SMTP not configured. Please contact your administrator.`,
},
[EAuthenticationErrorCodes.AUTHENTICATION_FAILED]: {
title: `Authentication failed.`,
message: `Authentication failed. Please try again.`,
},
[EAuthenticationErrorCodes.INVALID_TOKEN]: { title: `Invalid token.`, message: `Invalid token. Please try again.` },
[EAuthenticationErrorCodes.EXPIRED_TOKEN]: { title: `Expired token.`, message: `Expired token. Please try again.` },
[EAuthenticationErrorCodes.IMPROPERLY_CONFIGURED]: {
title: `Improperly configured.`,
message: `Improperly configured. Please contact your administrator.`,
},
[EAuthenticationErrorCodes.OAUTH_PROVIDER_ERROR]: {
title: `OAuth provider error.`,
message: `OAuth provider error. Please try again.`,
},
[EAuthenticationErrorCodes.INVALID_EMAIL]: {
title: `Invalid email.`,
message: `Invalid email. Please try again.`,
},
[EAuthenticationErrorCodes.INVALID_PASSWORD]: {
title: `Invalid password.`,
message: `Invalid password. Please try again.`,
},
[EAuthenticationErrorCodes.USER_DOES_NOT_EXIST]: {
title: `User does not exist.`,
message: `User does not exist. Please try again.`,
},
[EAuthenticationErrorCodes.ADMIN_ALREADY_EXIST]: {
title: `Admin already exists.`,
message: `Admin already exists. Please try again.`,
},
[EAuthenticationErrorCodes.USER_ALREADY_EXIST]: {
title: `User already exists.`,
message: `User already exists. Please try again.`,
},
[EAuthenticationErrorCodes.REQUIRED_EMAIL_PASSWORD_FIRST_NAME]: {
title: `Missing fields.`,
message: `Email, password, and first name are required.`,
},
[EAuthenticationErrorCodes.REQUIRED_EMAIL_PASSWORD]: {
title: `Missing fields.`,
message: `Email and password are required.`,
},
[EAuthenticationErrorCodes.EMAIL_CODE_REQUIRED]: {
title: `Missing fields.`,
message: `Email and code are required.`,
},
};
export const authErrorHandler = (
errorCode: EAuthenticationErrorCodes,
errorMessage: string | undefined
): TAuthErrorInfo | 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.INVALID_EMAIL,
EAuthenticationErrorCodes.INVALID_PASSWORD,
EAuthenticationErrorCodes.USER_DOES_NOT_EXIST,
EAuthenticationErrorCodes.ADMIN_ALREADY_EXIST,
EAuthenticationErrorCodes.USER_ALREADY_EXIST,
EAuthenticationErrorCodes.REQUIRED_EMAIL_PASSWORD_FIRST_NAME,
EAuthenticationErrorCodes.REQUIRED_EMAIL_PASSWORD,
EAuthenticationErrorCodes.EMAIL_CODE_REQUIRED,
];
if (toastAlertErrorCodes.includes(errorCode))
return {
type: EErrorAlertType.TOAST_ALERT,
title: errorCodeMessages[errorCode]?.title || "Error",
message: errorMessage || errorCodeMessages[errorCode]?.message || "Something went wrong. Please try again.",
};
if (bannerAlertErrorCodes.includes(errorCode))
return {
type: EErrorAlertType.BANNER_ALERT,
title: errorCodeMessages[errorCode]?.title || "Error",
message: errorMessage || errorCodeMessages[errorCode]?.message || "Something went wrong. Please try again.",
};
return undefined;
};

View File

@ -0,0 +1,290 @@
import { ReactNode } from "react";
import Link from "next/link";
export enum EPageTypes {
"PUBLIC" = "PUBLIC",
"NON_AUTHENTICATED" = "NON_AUTHENTICATED",
"ONBOARDING" = "ONBOARDING",
"AUTHENTICATED" = "AUTHENTICATED",
}
export enum EAuthModes {
SIGN_IN = "SIGN_IN",
SIGN_UP = "SIGN_UP",
}
export enum EAuthSteps {
EMAIL = "EMAIL",
PASSWORD = "PASSWORD",
UNIQUE_CODE = "UNIQUE_CODE",
}
export enum EAuthenticationErrorCodes {
INSTANCE_NOT_CONFIGURED = "5000",
SIGNUP_DISABLED = "5001",
INVALID_PASSWORD = "5002",
USER_ALREADY_EXIST = "5003",
USER_DOES_NOT_EXIST = "5004",
AUTHENTICATION_FAILED_SIGN_IN = "5005",
AUTHENTICATION_FAILED_SIGN_UP = "5006",
SMTP_NOT_CONFIGURED = "5007",
INVALID_MAGIC_CODE = "5008",
EXPIRED_MAGIC_CODE = "5009",
GOOGLE_NOT_CONFIGURED = "5010",
GITHUB_NOT_CONFIGURED = "5011",
INVALID_EMAIL = "5012",
EMAIL_REQUIRED = "5013",
REQUIRED_EMAIL_PASSWORD_SIGN_IN = "5014",
INVALID_EMAIL_SIGN_IN = "5015",
INVALID_EMAIL_SIGN_UP = "5016",
INVALID_EMAIL_MAGIC_SIGN_IN = "5017",
INVALID_EMAIL_MAGIC_SIGN_UP = "5018",
GITHUB_OAUTH_PROVIDER_ERROR = "5019",
GOOGLE_OAUTH_PROVIDER_ERROR = "5020",
MAGIC_SIGN_IN_EMAIL_CODE_REQUIRED = "5021",
MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED = "5022",
INVALID_PASSWORD_TOKEN = "5023",
EXPIRED_PASSWORD_TOKEN = "5024",
INCORRECT_OLD_PASSWORD = "5025",
INVALID_NEW_PASSWORD = "5026",
PASSWORD_ALREADY_SET = "5027",
ADMIN_ALREADY_EXIST = "5028",
REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME = "5029",
INVALID_ADMIN_EMAIL = "5030",
INVALID_ADMIN_PASSWORD = "5031",
REQUIRED_ADMIN_EMAIL_PASSWORD = "5032",
ADMIN_AUTHENTICATION_FAILED = "5034",
}
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 type TAuthErrorInfo = {
type: EErrorAlertType;
code: EAuthenticationErrorCodes;
title: string;
message: ReactNode;
};
const errorCodeMessages: { [key in EAuthenticationErrorCodes]: { title: string; message: ReactNode } } = {
[EAuthenticationErrorCodes.INSTANCE_NOT_CONFIGURED]: {
title: `Instance not configured`,
message: `Instance not configured. Please contact your administrator.`,
},
[EAuthenticationErrorCodes.SIGNUP_DISABLED]: {
title: `Sign up disabled`,
message: `Sign up disabled. Please contact your administrator.`,
},
[EAuthenticationErrorCodes.INVALID_PASSWORD]: {
title: `Invalid password`,
message: `Invalid password. Please try again.`,
},
[EAuthenticationErrorCodes.USER_ALREADY_EXIST]: {
title: `User already exists`,
message: (
<div>
Your account is already registered.&nbsp;
<Link
className="underline underline-offset-4 font-medium hover:font-bold transition-all"
href={`/accounts/sign-in`}
>
Sign In
</Link>
&nbsp;now.
</div>
),
},
[EAuthenticationErrorCodes.USER_DOES_NOT_EXIST]: {
title: `User does not exist`,
message: (
<div>
No account found.&nbsp;
<Link className="underline underline-offset-4 font-medium hover:font-bold transition-all" href={`/`}>
Create one
</Link>
&nbsp;to get started.
</div>
),
},
[EAuthenticationErrorCodes.AUTHENTICATION_FAILED_SIGN_IN]: {
title: `Authentication failed`,
message: `Authentication failed. Please try again.`,
},
[EAuthenticationErrorCodes.AUTHENTICATION_FAILED_SIGN_UP]: {
title: `Authentication failed`,
message: `Authentication failed. Please try again.`,
},
[EAuthenticationErrorCodes.SMTP_NOT_CONFIGURED]: {
title: `SMTP not configured`,
message: `SMTP not configured. Please contact your administrator.`,
},
[EAuthenticationErrorCodes.INVALID_MAGIC_CODE]: {
title: `Authentication failed`,
message: `Invalid magic code. Please try again.`,
},
[EAuthenticationErrorCodes.EXPIRED_MAGIC_CODE]: {
title: `Expired magic code`,
message: `Expired magic code. Please try again.`,
},
[EAuthenticationErrorCodes.GOOGLE_NOT_CONFIGURED]: {
title: `Google not configured`,
message: `Google not configured. Please contact your administrator.`,
},
[EAuthenticationErrorCodes.GITHUB_NOT_CONFIGURED]: {
title: `GitHub not configured`,
message: `GitHub not configured. Please contact your administrator.`,
},
[EAuthenticationErrorCodes.INVALID_EMAIL]: {
title: `Invalid email`,
message: `Invalid email. Please try again.`,
},
[EAuthenticationErrorCodes.EMAIL_REQUIRED]: {
title: `Email required`,
message: `Email required. Please try again.`,
},
[EAuthenticationErrorCodes.REQUIRED_EMAIL_PASSWORD_SIGN_IN]: {
title: `Email and password required`,
message: `Email and password required. Please try again.`,
},
[EAuthenticationErrorCodes.INVALID_EMAIL_SIGN_IN]: {
title: `Invalid email`,
message: `Invalid email. Please try again.`,
},
[EAuthenticationErrorCodes.INVALID_EMAIL_SIGN_UP]: {
title: `Invalid email`,
message: `Invalid email. Please try again.`,
},
[EAuthenticationErrorCodes.INVALID_EMAIL_MAGIC_SIGN_IN]: {
title: `Invalid email`,
message: `Invalid email. Please try again.`,
},
[EAuthenticationErrorCodes.INVALID_EMAIL_MAGIC_SIGN_UP]: {
title: `Invalid email`,
message: `Invalid email. Please try again.`,
},
[EAuthenticationErrorCodes.GITHUB_OAUTH_PROVIDER_ERROR]: {
title: `GitHub OAuth provider error`,
message: `GitHub OAuth provider error. Please try again.`,
},
[EAuthenticationErrorCodes.GOOGLE_OAUTH_PROVIDER_ERROR]: {
title: `Google OAuth provider error`,
message: `Google OAuth provider error. Please try again.`,
},
[EAuthenticationErrorCodes.MAGIC_SIGN_IN_EMAIL_CODE_REQUIRED]: {
title: `Email and code required`,
message: `Email and code required. Please try again.`,
},
[EAuthenticationErrorCodes.MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED]: {
title: `Email and code required`,
message: `Email and code required. Please try again.`,
},
[EAuthenticationErrorCodes.INVALID_PASSWORD_TOKEN]: {
title: `Invalid password token`,
message: `Invalid password token. Please try again.`,
},
[EAuthenticationErrorCodes.EXPIRED_PASSWORD_TOKEN]: {
title: `Expired password token`,
message: `Expired password token. Please try again.`,
},
[EAuthenticationErrorCodes.INCORRECT_OLD_PASSWORD]: {
title: `Incorrect old password`,
message: `Incorrect old password. Please try again.`,
},
[EAuthenticationErrorCodes.INVALID_NEW_PASSWORD]: {
title: `Invalid new password`,
message: `Invalid new password. Please try again.`,
},
[EAuthenticationErrorCodes.PASSWORD_ALREADY_SET]: {
title: `Password already set`,
message: `Password already set. Please try again.`,
},
[EAuthenticationErrorCodes.ADMIN_ALREADY_EXIST]: {
title: `Admin already exists`,
message: `Admin already exists. Please try again.`,
},
[EAuthenticationErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME]: {
title: `Email, password and first name required`,
message: `Email, password and first name required. Please try again.`,
},
[EAuthenticationErrorCodes.INVALID_ADMIN_EMAIL]: {
title: `Invalid email`,
message: `Invalid email. Please try again.`,
},
[EAuthenticationErrorCodes.INVALID_ADMIN_PASSWORD]: {
title: `Invalid password`,
message: `Invalid password. Please try again.`,
},
[EAuthenticationErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD]: {
title: `Email and password required`,
message: `Email and password required. Please try again.`,
},
[EAuthenticationErrorCodes.ADMIN_AUTHENTICATION_FAILED]: {
title: `Authentication failed`,
message: `Authentication failed. Please try again.`,
},
};
export const authErrorHandler = (errorCode: EAuthenticationErrorCodes): TAuthErrorInfo | undefined => {
const toastAlertErrorCodes = [
EAuthenticationErrorCodes.INSTANCE_NOT_CONFIGURED,
EAuthenticationErrorCodes.SIGNUP_DISABLED,
EAuthenticationErrorCodes.INVALID_PASSWORD,
EAuthenticationErrorCodes.AUTHENTICATION_FAILED_SIGN_IN,
EAuthenticationErrorCodes.AUTHENTICATION_FAILED_SIGN_UP,
EAuthenticationErrorCodes.SMTP_NOT_CONFIGURED,
EAuthenticationErrorCodes.INVALID_MAGIC_CODE,
EAuthenticationErrorCodes.EXPIRED_MAGIC_CODE,
EAuthenticationErrorCodes.GOOGLE_NOT_CONFIGURED,
EAuthenticationErrorCodes.GITHUB_NOT_CONFIGURED,
EAuthenticationErrorCodes.INVALID_EMAIL,
EAuthenticationErrorCodes.EMAIL_REQUIRED,
EAuthenticationErrorCodes.REQUIRED_EMAIL_PASSWORD_SIGN_IN,
EAuthenticationErrorCodes.INVALID_EMAIL_SIGN_IN,
EAuthenticationErrorCodes.INVALID_EMAIL_SIGN_UP,
EAuthenticationErrorCodes.INVALID_EMAIL_MAGIC_SIGN_IN,
EAuthenticationErrorCodes.INVALID_EMAIL_MAGIC_SIGN_UP,
EAuthenticationErrorCodes.GITHUB_OAUTH_PROVIDER_ERROR,
EAuthenticationErrorCodes.GOOGLE_OAUTH_PROVIDER_ERROR,
EAuthenticationErrorCodes.MAGIC_SIGN_IN_EMAIL_CODE_REQUIRED,
EAuthenticationErrorCodes.MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED,
EAuthenticationErrorCodes.INVALID_PASSWORD_TOKEN,
EAuthenticationErrorCodes.EXPIRED_PASSWORD_TOKEN,
EAuthenticationErrorCodes.INCORRECT_OLD_PASSWORD,
EAuthenticationErrorCodes.INVALID_NEW_PASSWORD,
EAuthenticationErrorCodes.PASSWORD_ALREADY_SET,
EAuthenticationErrorCodes.ADMIN_ALREADY_EXIST,
EAuthenticationErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME,
EAuthenticationErrorCodes.INVALID_ADMIN_EMAIL,
EAuthenticationErrorCodes.INVALID_ADMIN_PASSWORD,
EAuthenticationErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD,
EAuthenticationErrorCodes.ADMIN_AUTHENTICATION_FAILED,
];
const bannerAlertErrorCodes = [
EAuthenticationErrorCodes.USER_ALREADY_EXIST,
EAuthenticationErrorCodes.USER_DOES_NOT_EXIST,
];
if (toastAlertErrorCodes.includes(errorCode))
return {
type: EErrorAlertType.TOAST_ALERT,
code: errorCode,
title: errorCodeMessages[errorCode]?.title || "Error",
message: errorCodeMessages[errorCode]?.message || "Something went wrong. Please try again.",
};
if (bannerAlertErrorCodes.includes(errorCode))
return {
type: EErrorAlertType.BANNER_ALERT,
code: errorCode,
title: errorCodeMessages[errorCode]?.title || "Error",
message: errorCodeMessages[errorCode]?.message || "Something went wrong. Please try again.",
};
return undefined;
};

View File

@ -30,12 +30,7 @@ export const InstanceWrapper: FC<TInstanceWrapper> = observer((props) => {
);
// something went wrong while in the request
if (error && error?.status === "error")
return (
<div className="relative flex h-screen w-screen items-center justify-center">
Something went wrong. please try again later
</div>
);
if (error && error?.status === "error") return <>{children}</>;
// instance is not ready and setup is not done
if (instance?.instance?.is_setup_done === false) return <InstanceNotReady />;

View File

@ -122,7 +122,7 @@ const ForgotPasswordPage: NextPageWithLayout = () => {
</Link>
</div>
</div>
<div className="flex-grow container mx-auto max-w-lg px-10 lg:max-w-md lg:px-5 py-10">
<div className="flex-grow container mx-auto max-w-lg px-10 lg:max-w-md lg:px-5 py-10 lg:pt-28 transition-all">
<div className="relative flex flex-col space-y-6">
<div className="text-center space-y-1 py-4">
<h3 className="flex gap-4 justify-center text-3xl font-bold text-onboarding-text-100">
@ -176,7 +176,7 @@ const ForgotPasswordPage: NextPageWithLayout = () => {
>
{resendTimerCode > 0 ? `Resend in ${resendTimerCode} seconds` : "Send reset link"}
</Button>
<Link href="/" className={cn("w-full", getButtonStyling("link-neutral", "lg"))}>
<Link href="/accounts/sign-in" className={cn("w-full", getButtonStyling("link-neutral", "lg"))}>
Back to sign in
</Link>
</form>

View File

@ -90,7 +90,7 @@ const ResetPasswordPage: NextPageWithLayout = () => {
<span className="text-2xl font-semibold sm:text-3xl">Plane</span>
</div>
</div>
<div className="flex-grow container mx-auto max-w-lg px-10 lg:max-w-md lg:px-5 py-10">
<div className="flex-grow container mx-auto max-w-lg px-10 lg:max-w-md lg:px-5 py-10 lg:pt-28 transition-all">
<div className="relative flex flex-col space-y-6">
<div className="text-center space-y-1 py-4">
<h3 className="flex gap-4 justify-center text-3xl font-bold text-onboarding-text-100">

View File

@ -111,7 +111,7 @@ const SetPasswordPage: NextPageWithLayout = observer(() => {
<span className="text-2xl font-semibold sm:text-3xl">Plane</span>
</div>
</div>
<div className="flex-grow container mx-auto max-w-lg px-10 lg:max-w-md lg:px-5 py-10">
<div className="flex-grow container mx-auto max-w-lg px-10 lg:max-w-md lg:px-5 py-10 lg:pt-28 transition-all">
<div className="relative flex flex-col space-y-6">
<div className="text-center space-y-1 py-4">
<h3 className="flex gap-4 justify-center text-3xl font-bold text-onboarding-text-100">

View File

@ -4,12 +4,12 @@ import Link from "next/link";
// ui
import { useTheme } from "next-themes";
// components
import { SignInAuthRoot } from "@/components/account";
import { AuthRoot } from "@/components/account";
import { PageHead } from "@/components/core";
// constants
import { NAVIGATE_TO_SIGNUP } from "@/constants/event-tracker";
// helpers
import { EPageTypes } from "@/helpers/authentication.helper";
import { EAuthModes, EPageTypes } from "@/helpers/authentication.helper";
// hooks
import { useEventTracker } from "@/hooks/store";
// layouts
@ -58,8 +58,8 @@ const SignInPage: NextPageWithLayout = observer(() => {
</Link>
</div>
</div>
<div className="flex-grow container mx-auto max-w-lg px-10 lg:max-w-md lg:px-5 py-10">
<SignInAuthRoot />
<div className="flex-grow container mx-auto max-w-lg px-10 lg:max-w-md lg:px-5 py-10 lg:pt-28 transition-all">
<AuthRoot authMode={EAuthModes.SIGN_IN} />
</div>
</div>
</div>

View File

@ -5,12 +5,12 @@ import Link from "next/link";
// ui
import { useTheme } from "next-themes";
// components
import { SignUpAuthRoot } from "@/components/account";
import { AuthRoot } from "@/components/account";
import { PageHead } from "@/components/core";
// constants
import { NAVIGATE_TO_SIGNIN } from "@/constants/event-tracker";
// helpers
import { EPageTypes } from "@/helpers/authentication.helper";
import { EAuthModes, EPageTypes } from "@/helpers/authentication.helper";
// hooks
import { useEventTracker } from "@/hooks/store";
// layouts
@ -56,8 +56,8 @@ const HomePage: NextPageWithLayout = observer(() => {
</Link>
</div>
</div>
<div className="flex-grow container mx-auto max-w-lg px-10 lg:max-w-md lg:px-5 py-10">
<SignUpAuthRoot />
<div className="flex-grow container mx-auto max-w-lg px-10 lg:max-w-md lg:px-5 py-10 lg:pt-28 transition-all">
<AuthRoot authMode={EAuthModes.SIGN_UP} />
</div>
</div>
</div>