mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
chore: handled authentication in space and web apps
This commit is contained in:
parent
61f47258ee
commit
f0faf028c2
@ -120,7 +120,7 @@ class MagicCodeProvider(CredentialAdapter):
|
|||||||
"INVALID_MAGIC_CODE"
|
"INVALID_MAGIC_CODE"
|
||||||
],
|
],
|
||||||
error_message="INVALID_MAGIC_CODE",
|
error_message="INVALID_MAGIC_CODE",
|
||||||
payload={"email": str(self.key)},
|
payload={"email": str(email)},
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise AuthenticationException(
|
raise AuthenticationException(
|
||||||
|
@ -8,7 +8,7 @@ import { Button, Input, Spinner, TOAST_TYPE, setToast } from "@plane/ui";
|
|||||||
// components
|
// components
|
||||||
import { UserImageUploadModal } from "@/components/accounts";
|
import { UserImageUploadModal } from "@/components/accounts";
|
||||||
// hooks
|
// hooks
|
||||||
import { useMobxStore } from "@/lib/mobx/store-provider";
|
import { useMobxStore } from "@/hooks/store";
|
||||||
// services
|
// services
|
||||||
import fileService from "@/services/file.service";
|
import fileService from "@/services/file.service";
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ import { useRouter } from "next/router";
|
|||||||
import { IssueBlockDueDate } from "@/components/issues/board-views/block-due-date";
|
import { IssueBlockDueDate } from "@/components/issues/board-views/block-due-date";
|
||||||
import { IssueBlockPriority } from "@/components/issues/board-views/block-priority";
|
import { IssueBlockPriority } from "@/components/issues/board-views/block-priority";
|
||||||
import { IssueBlockState } from "@/components/issues/board-views/block-state";
|
import { IssueBlockState } from "@/components/issues/board-views/block-state";
|
||||||
import { useMobxStore } from "@/lib/mobx/store-provider";
|
import { useMobxStore } from "@/hooks/store";
|
||||||
|
|
||||||
// components
|
// components
|
||||||
// interfaces
|
// interfaces
|
||||||
|
@ -6,7 +6,7 @@ import { StateGroupIcon } from "@plane/ui";
|
|||||||
import { issueGroupFilter } from "@/constants/data";
|
import { issueGroupFilter } from "@/constants/data";
|
||||||
// ui
|
// ui
|
||||||
// mobx hook
|
// mobx hook
|
||||||
import { useMobxStore } from "@/lib/mobx/store-provider";
|
import { useMobxStore } from "@/hooks/store";
|
||||||
import { RootStore } from "@/store/root.store";
|
import { RootStore } from "@/store/root.store";
|
||||||
import { IIssueState } from "types/issue";
|
import { IIssueState } from "types/issue";
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ import { IssueKanBanHeader } from "@/components/issues/board-views/kanban/header
|
|||||||
import { Icon } from "@/components/ui";
|
import { Icon } from "@/components/ui";
|
||||||
// interfaces
|
// interfaces
|
||||||
// mobx hook
|
// mobx hook
|
||||||
import { useMobxStore } from "@/lib/mobx/store-provider";
|
import { useMobxStore } from "@/hooks/store";
|
||||||
import { RootStore } from "@/store/root.store";
|
import { RootStore } from "@/store/root.store";
|
||||||
import { IIssueState, IIssue } from "types/issue";
|
import { IIssueState, IIssue } from "types/issue";
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import { IssueBlockLabels } from "@/components/issues/board-views/block-labels";
|
|||||||
import { IssueBlockPriority } from "@/components/issues/board-views/block-priority";
|
import { IssueBlockPriority } from "@/components/issues/board-views/block-priority";
|
||||||
import { IssueBlockState } from "@/components/issues/board-views/block-state";
|
import { IssueBlockState } from "@/components/issues/board-views/block-state";
|
||||||
// mobx hook
|
// mobx hook
|
||||||
import { useMobxStore } from "@/lib/mobx/store-provider";
|
import { useMobxStore } from "@/hooks/store";
|
||||||
// interfaces
|
// interfaces
|
||||||
import { RootStore } from "@/store/root.store";
|
import { RootStore } from "@/store/root.store";
|
||||||
import { IIssue } from "types/issue";
|
import { IIssue } from "types/issue";
|
||||||
|
@ -6,7 +6,7 @@ import { StateGroupIcon } from "@plane/ui";
|
|||||||
// constants
|
// constants
|
||||||
import { issueGroupFilter } from "@/constants/data";
|
import { issueGroupFilter } from "@/constants/data";
|
||||||
// mobx hook
|
// mobx hook
|
||||||
import { useMobxStore } from "@/lib/mobx/store-provider";
|
import { useMobxStore } from "@/hooks/store";
|
||||||
import { RootStore } from "@/store/root.store";
|
import { RootStore } from "@/store/root.store";
|
||||||
import { IIssueState } from "types/issue";
|
import { IIssueState } from "types/issue";
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import { IssueListBlock } from "@/components/issues/board-views/list/block";
|
|||||||
import { IssueListHeader } from "@/components/issues/board-views/list/header";
|
import { IssueListHeader } from "@/components/issues/board-views/list/header";
|
||||||
// interfaces
|
// interfaces
|
||||||
// mobx hook
|
// mobx hook
|
||||||
import { useMobxStore } from "@/lib/mobx/store-provider";
|
import { useMobxStore } from "@/hooks/store";
|
||||||
// store
|
// store
|
||||||
import { RootStore } from "@/store/root.store";
|
import { RootStore } from "@/store/root.store";
|
||||||
import { IIssueState, IIssue } from "types/issue";
|
import { IIssueState, IIssue } from "types/issue";
|
||||||
|
@ -3,7 +3,7 @@ import { observer } from "mobx-react-lite";
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
// components
|
// components
|
||||||
// store
|
// store
|
||||||
import { useMobxStore } from "@/lib/mobx/store-provider";
|
import { useMobxStore } from "@/hooks/store";
|
||||||
import { IIssueFilterOptions } from "@/store/issues/types";
|
import { IIssueFilterOptions } from "@/store/issues/types";
|
||||||
import { RootStore } from "@/store/root.store";
|
import { RootStore } from "@/store/root.store";
|
||||||
import { AppliedFiltersList } from "./filters-list";
|
import { AppliedFiltersList } from "./filters-list";
|
||||||
|
@ -2,7 +2,7 @@ import { FC, useCallback } from "react";
|
|||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
// components
|
// components
|
||||||
import { useMobxStore } from "@/lib/mobx/store-provider";
|
import { useMobxStore } from "@/hooks/store";
|
||||||
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/store/issues/helpers";
|
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/store/issues/helpers";
|
||||||
import { IIssueFilterOptions } from "@/store/issues/types";
|
import { IIssueFilterOptions } from "@/store/issues/types";
|
||||||
import { RootStore } from "@/store/root.store";
|
import { RootStore } from "@/store/root.store";
|
||||||
|
@ -8,8 +8,7 @@ import { Avatar, Button } from "@plane/ui";
|
|||||||
import { ProjectLogo } from "@/components/common";
|
import { ProjectLogo } from "@/components/common";
|
||||||
import { IssueFiltersDropdown } from "@/components/issues/filters";
|
import { IssueFiltersDropdown } from "@/components/issues/filters";
|
||||||
// hooks
|
// hooks
|
||||||
import { useUser } from "@/hooks/store";
|
import { useMobxStore, useUser } from "@/hooks/store";
|
||||||
import { useMobxStore } from "@/lib/mobx/store-provider";
|
|
||||||
// store
|
// store
|
||||||
import { RootStore } from "@/store/root.store";
|
import { RootStore } from "@/store/root.store";
|
||||||
import { TIssueBoardKeys } from "@/types/issue";
|
import { TIssueBoardKeys } from "@/types/issue";
|
||||||
|
@ -3,7 +3,7 @@ import { useRouter } from "next/router";
|
|||||||
// constants
|
// constants
|
||||||
import { issueViews } from "@/constants/data";
|
import { issueViews } from "@/constants/data";
|
||||||
// mobx
|
// mobx
|
||||||
import { useMobxStore } from "@/lib/mobx/store-provider";
|
import { useMobxStore } from "@/hooks/store";
|
||||||
import { RootStore } from "@/store/root.store";
|
import { RootStore } from "@/store/root.store";
|
||||||
import { TIssueBoardKeys } from "types/issue";
|
import { TIssueBoardKeys } from "types/issue";
|
||||||
|
|
||||||
|
@ -6,10 +6,8 @@ import { useForm, Controller } from "react-hook-form";
|
|||||||
import { EditorRefApi } from "@plane/lite-text-editor";
|
import { EditorRefApi } from "@plane/lite-text-editor";
|
||||||
import { LiteTextEditor } from "@/components/editor/lite-text-editor";
|
import { LiteTextEditor } from "@/components/editor/lite-text-editor";
|
||||||
// hooks
|
// hooks
|
||||||
import { useUser } from "@/hooks/store";
|
import { useMobxStore, useUser } from "@/hooks/store";
|
||||||
import useToast from "@/hooks/use-toast";
|
import useToast from "@/hooks/use-toast";
|
||||||
// lib
|
|
||||||
import { useMobxStore } from "@/lib/mobx/store-provider";
|
|
||||||
// types
|
// types
|
||||||
import { Comment } from "@/types/issue";
|
import { Comment } from "@/types/issue";
|
||||||
|
|
||||||
|
@ -10,9 +10,7 @@ import { CommentReactions } from "@/components/issues/peek-overview";
|
|||||||
// helpers
|
// helpers
|
||||||
import { timeAgo } from "@/helpers/date-time.helper";
|
import { timeAgo } from "@/helpers/date-time.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useUser } from "@/hooks/store";
|
import { useMobxStore, useUser } from "@/hooks/store";
|
||||||
// mobx store
|
|
||||||
import { useMobxStore } from "@/lib/mobx/store-provider";
|
|
||||||
// store
|
// store
|
||||||
import { RootStore } from "@/store/root.store";
|
import { RootStore } from "@/store/root.store";
|
||||||
// types
|
// types
|
||||||
|
@ -1,16 +1,13 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
// mobx
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
// ui
|
|
||||||
import { Tooltip } from "@plane/ui";
|
import { Tooltip } from "@plane/ui";
|
||||||
|
// ui
|
||||||
import { ReactionSelector } from "@/components/ui";
|
import { ReactionSelector } from "@/components/ui";
|
||||||
// helpers
|
// helpers
|
||||||
import { groupReactions, renderEmoji } from "@/helpers/emoji.helper";
|
import { groupReactions, renderEmoji } from "@/helpers/emoji.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useUser } from "@/hooks/store";
|
import { useMobxStore, useUser } from "@/hooks/store";
|
||||||
import { useMobxStore } from "@/lib/mobx/store-provider";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
commentId: string;
|
commentId: string;
|
||||||
|
@ -8,7 +8,7 @@ import { Icon } from "@/components/ui";
|
|||||||
// helpers
|
// helpers
|
||||||
import { copyTextToClipboard } from "@/helpers/string.helper";
|
import { copyTextToClipboard } from "@/helpers/string.helper";
|
||||||
// store
|
// store
|
||||||
import { useMobxStore } from "@/lib/mobx/store-provider";
|
import { useMobxStore } from "@/hooks/store";
|
||||||
import { IPeekMode } from "@/store/issue_details";
|
import { IPeekMode } from "@/store/issue_details";
|
||||||
import { RootStore } from "@/store/root.store";
|
import { RootStore } from "@/store/root.store";
|
||||||
// lib
|
// lib
|
||||||
|
@ -2,13 +2,12 @@ import React from "react";
|
|||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
// lib
|
|
||||||
import { Button } from "@plane/ui";
|
import { Button } from "@plane/ui";
|
||||||
|
// components
|
||||||
import { CommentCard, AddComment } from "@/components/issues/peek-overview";
|
import { CommentCard, AddComment } from "@/components/issues/peek-overview";
|
||||||
import { Icon } from "@/components/ui";
|
import { Icon } from "@/components/ui";
|
||||||
// hooks
|
// hooks
|
||||||
import { useUser } from "@/hooks/store";
|
import { useMobxStore, useUser } from "@/hooks/store";
|
||||||
import { useMobxStore } from "@/lib/mobx/store-provider";
|
|
||||||
// types
|
// types
|
||||||
import { IIssue } from "@/types/issue";
|
import { IIssue } from "@/types/issue";
|
||||||
|
|
||||||
|
@ -4,12 +4,10 @@ import { useRouter } from "next/router";
|
|||||||
// lib
|
// lib
|
||||||
import { Tooltip } from "@plane/ui";
|
import { Tooltip } from "@plane/ui";
|
||||||
import { ReactionSelector } from "@/components/ui";
|
import { ReactionSelector } from "@/components/ui";
|
||||||
|
// helpers
|
||||||
import { groupReactions, renderEmoji } from "@/helpers/emoji.helper";
|
import { groupReactions, renderEmoji } from "@/helpers/emoji.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useUser } from "@/hooks/store";
|
import { useMobxStore, useUser } from "@/hooks/store";
|
||||||
import { useMobxStore } from "@/lib/mobx/store-provider";
|
|
||||||
// helpers
|
|
||||||
// components
|
|
||||||
|
|
||||||
export const IssueEmojiReactions: React.FC = observer(() => {
|
export const IssueEmojiReactions: React.FC = observer(() => {
|
||||||
// router
|
// router
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { IssueEmojiReactions, IssueVotes } from "@/components/issues/peek-overview";
|
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 = () => {
|
export const IssueReactions: React.FC = () => {
|
||||||
const { project: projectStore } = useMobxStore();
|
const { project: projectStore } = useMobxStore();
|
||||||
|
@ -1,15 +1,9 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
// mobx
|
|
||||||
// lib
|
|
||||||
import { Tooltip } from "@plane/ui";
|
import { Tooltip } from "@plane/ui";
|
||||||
// hooks
|
// hooks
|
||||||
import { useUser } from "@/hooks/store";
|
import { useMobxStore, useUser } from "@/hooks/store";
|
||||||
import { useMobxStore } from "@/lib/mobx/store-provider";
|
|
||||||
// ui
|
|
||||||
|
|
||||||
export const IssueVotes: React.FC = observer(() => {
|
export const IssueVotes: React.FC = observer(() => {
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
@ -9,7 +9,7 @@ import { Dialog, Transition } from "@headlessui/react";
|
|||||||
// components
|
// components
|
||||||
import { FullScreenPeekView, SidePeekView } from "@/components/issues/peek-overview";
|
import { FullScreenPeekView, SidePeekView } from "@/components/issues/peek-overview";
|
||||||
// lib
|
// lib
|
||||||
import { useMobxStore } from "@/lib/mobx/store-provider";
|
import { useMobxStore } from "@/hooks/store";
|
||||||
|
|
||||||
export const IssuePeekOverview: React.FC = observer(() => {
|
export const IssuePeekOverview: React.FC = observer(() => {
|
||||||
// states
|
// states
|
||||||
|
@ -20,14 +20,14 @@ export const AuthView = observer(() => {
|
|||||||
const { data: currentUser, fetchCurrentUser, isLoading } = useUser();
|
const { data: currentUser, fetchCurrentUser, isLoading } = useUser();
|
||||||
|
|
||||||
// fetching user information
|
// fetching user information
|
||||||
useSWR("CURRENT_USER_DETAILS", () => fetchCurrentUser(), {
|
const { isLoading: isSWRLoading } = useSWR("CURRENT_USER_DETAILS", () => fetchCurrentUser(), {
|
||||||
shouldRetryOnError: false,
|
shouldRetryOnError: false,
|
||||||
revalidateOnFocus: false,
|
revalidateOnFocus: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isLoading ? (
|
{isLoading || isSWRLoading ? (
|
||||||
<div className="relative flex h-screen w-screen items-center justify-center">
|
<div className="relative flex h-screen w-screen items-center justify-center">
|
||||||
<Spinner />
|
<Spinner />
|
||||||
</div>
|
</div>
|
||||||
|
@ -11,8 +11,7 @@ import { IssueSpreadsheetView } from "@/components/issues/board-views/spreadshee
|
|||||||
import { IssueAppliedFilters } from "@/components/issues/filters/applied-filters/root";
|
import { IssueAppliedFilters } from "@/components/issues/filters/applied-filters/root";
|
||||||
import { IssuePeekOverview } from "@/components/issues/peek-overview";
|
import { IssuePeekOverview } from "@/components/issues/peek-overview";
|
||||||
// mobx store
|
// mobx store
|
||||||
import { useUser } from "@/hooks/store";
|
import { useMobxStore, useUser } from "@/hooks/store";
|
||||||
import { useMobxStore } from "@/lib/mobx/store-provider";
|
|
||||||
import { RootStore } from "@/store/root.store";
|
import { RootStore } from "@/store/root.store";
|
||||||
// assets
|
// assets
|
||||||
import SomethingWentWrongImage from "public/something-went-wrong.svg";
|
import SomethingWentWrongImage from "public/something-went-wrong.svg";
|
||||||
|
291
space/helpers/authentication.helper.tsx
Normal file
291
space/helpers/authentication.helper.tsx
Normal 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.
|
||||||
|
<Link
|
||||||
|
className="underline underline-offset-4 font-medium hover:font-bold transition-all"
|
||||||
|
href={`/accounts/sign-in`}
|
||||||
|
>
|
||||||
|
Sign In
|
||||||
|
</Link>
|
||||||
|
now.
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
[EAuthenticationErrorCodes.USER_DOES_NOT_EXIST]: {
|
||||||
|
title: `User does not exist`,
|
||||||
|
message: (
|
||||||
|
<div>
|
||||||
|
No account found.
|
||||||
|
<Link className="underline underline-offset-4 font-medium hover:font-bold transition-all" href={`/`}>
|
||||||
|
Create one
|
||||||
|
</Link>
|
||||||
|
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;
|
||||||
|
};
|
@ -1,2 +1,4 @@
|
|||||||
|
export * from "./user-mobx-provider";
|
||||||
|
|
||||||
export * from "./use-instance";
|
export * from "./use-instance";
|
||||||
export * from "./user";
|
export * from "./user";
|
||||||
|
10
space/hooks/store/user-mobx-provider.ts
Normal file
10
space/hooks/store/user-mobx-provider.ts
Normal 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;
|
||||||
|
};
|
@ -1,4 +1,4 @@
|
|||||||
import { useMobxStore } from "@/lib/mobx/store-provider";
|
import { useMobxStore } from "@/hooks/store";
|
||||||
import { RootStore } from "@/store/root.store";
|
import { RootStore } from "@/store/root.store";
|
||||||
|
|
||||||
const useEditorSuggestions = () => {
|
const useEditorSuggestions = () => {
|
||||||
|
@ -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;
|
|
||||||
};
|
|
86
space/lib/wrappers/auth-wrapper.tsx
Normal file
86
space/lib/wrappers/auth-wrapper.tsx
Normal 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}</>;
|
||||||
|
});
|
2
space/lib/wrappers/index.ts
Normal file
2
space/lib/wrappers/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./instance-wrapper";
|
||||||
|
export * from "./auth-wrapper";
|
@ -8,20 +8,20 @@ import { InstanceNotReady } from "@/components/instance";
|
|||||||
// hooks
|
// hooks
|
||||||
import { useInstance } from "@/hooks/store";
|
import { useInstance } from "@/hooks/store";
|
||||||
|
|
||||||
type TInstanceLayout = {
|
type TInstanceWrapper = {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
const InstanceLayout: FC<TInstanceLayout> = observer((props) => {
|
export const InstanceWrapper: FC<TInstanceWrapper> = observer((props) => {
|
||||||
const { children } = props;
|
const { children } = props;
|
||||||
// store
|
// hooks
|
||||||
const { isLoading, instance, fetchInstanceInfo } = useInstance();
|
const { isLoading, instance, fetchInstanceInfo } = useInstance();
|
||||||
|
|
||||||
useSWR("INSTANCE_INFORMATION", () => fetchInstanceInfo(), {
|
const { isLoading: isSWRLoading } = useSWR("INSTANCE_INFORMATION", () => fetchInstanceInfo(), {
|
||||||
revalidateOnFocus: false,
|
revalidateOnFocus: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isLoading)
|
if (isSWRLoading || isLoading)
|
||||||
return (
|
return (
|
||||||
<div className="relative flex h-screen w-full items-center justify-center">
|
<div className="relative flex h-screen w-full items-center justify-center">
|
||||||
<Spinner />
|
<Spinner />
|
||||||
@ -32,5 +32,3 @@ const InstanceLayout: FC<TInstanceLayout> = observer((props) => {
|
|||||||
|
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
});
|
});
|
||||||
|
|
||||||
export default InstanceLayout;
|
|
@ -1,14 +1,16 @@
|
|||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
/// layouts
|
|
||||||
// components
|
// components
|
||||||
import { ProjectDetailsView } from "@/components/views";
|
import { ProjectDetailsView } from "@/components/views";
|
||||||
// lib
|
// helpers
|
||||||
import { useMobxStore } from "@/lib/mobx/store-provider";
|
import { EPageTypes } from "@/helpers/authentication.helper";
|
||||||
import ProjectLayout from "layouts/project-layout";
|
// hooks
|
||||||
|
import { useMobxStore } from "@/hooks/store";
|
||||||
|
// layouts
|
||||||
|
import ProjectLayout from "@/layouts/project-layout";
|
||||||
|
// wrappers
|
||||||
|
import { AuthWrapper } from "@/lib/wrappers";
|
||||||
|
|
||||||
const WorkspaceProjectPage = (props: any) => {
|
const WorkspaceProjectPage = (props: any) => {
|
||||||
const SITE_TITLE = props?.project_settings?.project_details?.name || "Plane | Deploy";
|
const SITE_TITLE = props?.project_settings?.project_details?.name || "Plane | Deploy";
|
||||||
@ -31,12 +33,14 @@ const WorkspaceProjectPage = (props: any) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ProjectLayout>
|
<AuthWrapper pageType={EPageTypes.AUTHENTICATED}>
|
||||||
<Head>
|
<ProjectLayout>
|
||||||
<title>{SITE_TITLE}</title>
|
<Head>
|
||||||
</Head>
|
<title>{SITE_TITLE}</title>
|
||||||
<ProjectDetailsView />
|
</Head>
|
||||||
</ProjectLayout>
|
<ProjectDetailsView />
|
||||||
|
</ProjectLayout>
|
||||||
|
</AuthWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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 { SITE_NAME, SITE_DESCRIPTION, SITE_URL, TWITTER_USER_NAME, SITE_KEYWORDS, SITE_TITLE } from "@/constants/seo";
|
||||||
import { ToastContextProvider } from "@/contexts/toast.context";
|
import { ToastContextProvider } from "@/contexts/toast.context";
|
||||||
// mobx store provider
|
// mobx store provider
|
||||||
import InstanceLayout from "@/layouts/instance-layout";
|
import { StoreProvider } from "@/lib/store-context";
|
||||||
import { MobxStoreProvider } from "@/lib/mobx/store-provider";
|
// wrappers
|
||||||
// constants
|
import { InstanceWrapper } from "@/lib/wrappers";
|
||||||
|
|
||||||
const prefix = parseInt(process.env.NEXT_PUBLIC_DEPLOY_WITH_NGINX || "0") === 0 ? "/" : "/spaces/";
|
const prefix = parseInt(process.env.NEXT_PUBLIC_DEPLOY_WITH_NGINX || "0") === 0 ? "/" : "/spaces/";
|
||||||
|
|
||||||
function MyApp({ Component, pageProps }: AppProps) {
|
function MyApp({ Component, pageProps }: AppProps) {
|
||||||
return (
|
return (
|
||||||
<MobxStoreProvider>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{SITE_TITLE}</title>
|
<title>{SITE_TITLE}</title>
|
||||||
<meta property="og:site_name" content={SITE_NAME} />
|
<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="manifest" href={`${prefix}site.webmanifest.json`} />
|
||||||
<link rel="shortcut icon" href={`${prefix}favicon/favicon.ico`} />
|
<link rel="shortcut icon" href={`${prefix}favicon/favicon.ico`} />
|
||||||
</Head>
|
</Head>
|
||||||
<ToastContextProvider>
|
<StoreProvider>
|
||||||
<ThemeProvider themes={["light", "dark"]} defaultTheme="system" enableSystem>
|
<ToastContextProvider>
|
||||||
<InstanceLayout>
|
<ThemeProvider themes={["light", "dark"]} defaultTheme="system" enableSystem>
|
||||||
<Component {...pageProps} />
|
<InstanceWrapper>
|
||||||
</InstanceLayout>
|
<Component {...pageProps} />
|
||||||
</ThemeProvider>
|
</InstanceWrapper>
|
||||||
</ToastContextProvider>
|
</ThemeProvider>
|
||||||
</MobxStoreProvider>
|
</ToastContextProvider>
|
||||||
|
</StoreProvider>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,11 +9,13 @@ import { CircleCheck } from "lucide-react";
|
|||||||
// ui
|
// ui
|
||||||
import { Button, Input, TOAST_TYPE, getButtonStyling, setToast } from "@plane/ui";
|
import { Button, Input, TOAST_TYPE, getButtonStyling, setToast } from "@plane/ui";
|
||||||
// helpers
|
// helpers
|
||||||
|
import { EPageTypes } from "@/helpers/authentication.helper";
|
||||||
import { cn } from "@/helpers/common.helper";
|
import { cn } from "@/helpers/common.helper";
|
||||||
import { checkEmailValidity } from "@/helpers/string.helper";
|
import { checkEmailValidity } from "@/helpers/string.helper";
|
||||||
// hooks
|
// hooks
|
||||||
// import useAuthRedirection from "@/hooks/use-auth-redirection";
|
|
||||||
import useTimer from "@/hooks/use-timer";
|
import useTimer from "@/hooks/use-timer";
|
||||||
|
// wrappers
|
||||||
|
import { AuthWrapper } from "@/lib/wrappers";
|
||||||
// services
|
// services
|
||||||
import { AuthService } from "@/services/authentication.service";
|
import { AuthService } from "@/services/authentication.service";
|
||||||
// images
|
// images
|
||||||
@ -77,85 +79,87 @@ const ForgotPasswordPage: NextPage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative h-screen w-full overflow-hidden">
|
<AuthWrapper pageType={EPageTypes.NON_AUTHENTICATED}>
|
||||||
<div className="absolute inset-0 z-0">
|
<div className="relative h-screen w-full overflow-hidden">
|
||||||
<Image
|
<div className="absolute inset-0 z-0">
|
||||||
src={resolvedTheme === "dark" ? PlaneBackgroundPatternDark : PlaneBackgroundPattern}
|
<Image
|
||||||
className="w-full h-full object-cover"
|
src={resolvedTheme === "dark" ? PlaneBackgroundPatternDark : PlaneBackgroundPattern}
|
||||||
alt="Plane background pattern"
|
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>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="mx-auto h-full">
|
<div className="relative z-10">
|
||||||
<div className="h-full overflow-auto px-7 pb-56 pt-4 sm:px-0">
|
<div className="flex items-center justify-between px-8 pb-4 sm:px-16 sm:py-5 lg:px-28">
|
||||||
<div className="mx-auto flex flex-col">
|
<div className="flex items-center gap-x-2 py-10">
|
||||||
<div className="text-center space-y-1 py-4 mx-auto sm:w-96">
|
<Image src={BluePlaneLogoWithoutText} height={30} width={30} alt="Plane Logo" className="mr-2" />
|
||||||
<h3 className="flex gap-4 justify-center text-3xl font-bold text-onboarding-text-100">
|
<span className="text-2xl font-semibold sm:text-3xl">Plane</span>
|
||||||
Reset your password
|
</div>
|
||||||
</h3>
|
</div>
|
||||||
<p className="font-medium text-onboarding-text-400">
|
<div className="mx-auto h-full">
|
||||||
Enter your user account{"'"}s verified email address and we will send you a password reset link.
|
<div className="h-full overflow-auto px-7 pb-56 pt-4 sm:px-0">
|
||||||
</p>
|
<div className="mx-auto flex flex-col">
|
||||||
</div>
|
<div className="text-center space-y-1 py-4 mx-auto sm:w-96">
|
||||||
<form onSubmit={handleSubmit(handleForgotPassword)} className="mx-auto mt-5 space-y-4 w-5/6 sm:w-96">
|
<h3 className="flex gap-4 justify-center text-3xl font-bold text-onboarding-text-100">
|
||||||
<div className="space-y-1">
|
Reset your password
|
||||||
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="email">
|
</h3>
|
||||||
Email
|
<p className="font-medium text-onboarding-text-400">
|
||||||
</label>
|
Enter your user account{"'"}s verified email address and we will send you a password reset link.
|
||||||
<Controller
|
</p>
|
||||||
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>
|
</div>
|
||||||
<Button
|
<form onSubmit={handleSubmit(handleForgotPassword)} className="mx-auto mt-5 space-y-4 w-5/6 sm:w-96">
|
||||||
type="submit"
|
<div className="space-y-1">
|
||||||
variant="primary"
|
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="email">
|
||||||
className="w-full"
|
Email
|
||||||
size="lg"
|
</label>
|
||||||
disabled={!isValid}
|
<Controller
|
||||||
loading={isSubmitting || resendTimerCode > 0}
|
control={control}
|
||||||
>
|
name="email"
|
||||||
{resendTimerCode > 0 ? `Resend in ${resendTimerCode} seconds` : "Send reset link"}
|
rules={{
|
||||||
</Button>
|
required: "Email is required",
|
||||||
<Link href="/" className={cn("w-full", getButtonStyling("link-neutral", "lg"))}>
|
validate: (value) => checkEmailValidity(value) || "Email is invalid",
|
||||||
Back to sign in
|
}}
|
||||||
</Link>
|
render={({ field: { value, onChange, ref } }) => (
|
||||||
</form>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</AuthWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -10,9 +10,11 @@ import { Button, Input } from "@plane/ui";
|
|||||||
// components
|
// components
|
||||||
import { PasswordStrengthMeter } from "@/components/accounts";
|
import { PasswordStrengthMeter } from "@/components/accounts";
|
||||||
// helpers
|
// helpers
|
||||||
|
import { EPageTypes } from "@/helpers/authentication.helper";
|
||||||
import { API_BASE_URL } from "@/helpers/common.helper";
|
import { API_BASE_URL } from "@/helpers/common.helper";
|
||||||
import { getPasswordStrength } from "@/helpers/password.helper";
|
import { getPasswordStrength } from "@/helpers/password.helper";
|
||||||
// hooks
|
// wrappers
|
||||||
|
import { AuthWrapper } from "@/lib/wrappers";
|
||||||
// services
|
// services
|
||||||
import { AuthService } from "@/services/authentication.service";
|
import { AuthService } from "@/services/authentication.service";
|
||||||
// images
|
// images
|
||||||
@ -75,98 +77,71 @@ const ResetPasswordPage: NextPage = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative h-screen w-full overflow-hidden">
|
<AuthWrapper pageType={EPageTypes.NON_AUTHENTICATED}>
|
||||||
<div className="absolute inset-0 z-0">
|
<div className="relative h-screen w-full overflow-hidden">
|
||||||
<Image
|
<div className="absolute inset-0 z-0">
|
||||||
src={resolvedTheme === "dark" ? PlaneBackgroundPatternDark : PlaneBackgroundPattern}
|
<Image
|
||||||
className="w-full h-full object-cover"
|
src={resolvedTheme === "dark" ? PlaneBackgroundPatternDark : PlaneBackgroundPattern}
|
||||||
alt="Plane background pattern"
|
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>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="mx-auto h-full">
|
<div className="relative z-10">
|
||||||
<div className="h-full overflow-auto px-7 pb-56 pt-4 sm:px-0">
|
<div className="flex items-center justify-between px-8 pb-4 sm:px-16 sm:py-5 lg:px-28">
|
||||||
<div className="mx-auto flex flex-col">
|
<div className="flex items-center gap-x-2 py-10">
|
||||||
<div className="text-center space-y-1 py-4 mx-auto sm:w-96">
|
<Image src={BluePlaneLogoWithoutText} height={30} width={30} alt="Plane Logo" className="mr-2" />
|
||||||
<h3 className="flex gap-4 justify-center text-3xl font-bold text-onboarding-text-100">
|
<span className="text-2xl font-semibold sm:text-3xl">Plane</span>
|
||||||
Set new password
|
</div>
|
||||||
</h3>
|
</div>
|
||||||
<p className="font-medium text-onboarding-text-400">Secure your account with a strong password</p>
|
<div className="mx-auto h-full">
|
||||||
</div>
|
<div className="h-full overflow-auto px-7 pb-56 pt-4 sm:px-0">
|
||||||
<form
|
<div className="mx-auto flex flex-col">
|
||||||
className="mx-auto mt-5 space-y-4 w-5/6 sm:w-96"
|
<div className="text-center space-y-1 py-4 mx-auto sm:w-96">
|
||||||
method="POST"
|
<h3 className="flex gap-4 justify-center text-3xl font-bold text-onboarding-text-100">
|
||||||
action={`${API_BASE_URL}/auth/reset-password/${uidb64?.toString()}/${token?.toString()}/`}
|
Set new password
|
||||||
>
|
</h3>
|
||||||
<input type="hidden" name="csrfmiddlewaretoken" value={csrfToken} />
|
<p className="font-medium text-onboarding-text-400">Secure your account with a strong password</p>
|
||||||
<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>
|
</div>
|
||||||
<div className="space-y-1">
|
<form
|
||||||
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="password">
|
className="mx-auto mt-5 space-y-4 w-5/6 sm:w-96"
|
||||||
Password
|
method="POST"
|
||||||
</label>
|
action={`${API_BASE_URL}/auth/reset-password/${uidb64?.toString()}/${token?.toString()}/`}
|
||||||
<div className="relative flex items-center rounded-md bg-onboarding-background-200">
|
>
|
||||||
<Input
|
<input type="hidden" name="csrfmiddlewaretoken" value={csrfToken} />
|
||||||
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 && (
|
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="confirm_password">
|
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="email">
|
||||||
Confirm password
|
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>
|
</label>
|
||||||
<div className="relative flex items-center rounded-md bg-onboarding-background-200">
|
<div className="relative flex items-center rounded-md bg-onboarding-background-200">
|
||||||
<Input
|
<Input
|
||||||
type={showPassword ? "text" : "password"}
|
type={showPassword ? "text" : "password"}
|
||||||
name="confirm_password"
|
name="password"
|
||||||
value={resetFormData.confirm_password}
|
value={resetFormData.password}
|
||||||
onChange={(e) => handleFormChange("confirm_password", e.target.value)}
|
onChange={(e) => handleFormChange("password", e.target.value)}
|
||||||
placeholder="Confirm password"
|
//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"
|
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 ? (
|
{showPassword ? (
|
||||||
<EyeOff
|
<EyeOff
|
||||||
@ -180,20 +155,50 @@ const ResetPasswordPage: NextPage = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{!!resetFormData.confirm_password && resetFormData.password !== resetFormData.confirm_password && (
|
{isPasswordInputFocused && <PasswordStrengthMeter password={resetFormData.password} />}
|
||||||
<span className="text-sm text-red-500">Passwords don{"'"}t match</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
{getPasswordStrength(resetFormData.password) >= 3 && (
|
||||||
<Button type="submit" variant="primary" className="w-full" size="lg" disabled={isButtonDisabled}>
|
<div className="space-y-1">
|
||||||
Set password
|
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="confirm_password">
|
||||||
</Button>
|
Confirm password
|
||||||
</form>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</AuthWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2,8 +2,15 @@ import { observer } from "mobx-react-lite";
|
|||||||
import { NextPage } from "next";
|
import { NextPage } from "next";
|
||||||
// components
|
// components
|
||||||
import { AuthView } from "@/components/views";
|
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;
|
export default Index;
|
||||||
|
@ -7,8 +7,12 @@ import { useTheme } from "next-themes";
|
|||||||
import { Avatar } from "@plane/ui";
|
import { Avatar } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { OnBoardingForm } from "@/components/accounts/onboarding-form";
|
import { OnBoardingForm } from "@/components/accounts/onboarding-form";
|
||||||
// mobx
|
// helpers
|
||||||
|
import { EPageTypes } from "@/helpers/authentication.helper";
|
||||||
|
// hooks
|
||||||
import { useUser, useUserProfile } from "@/hooks/store";
|
import { useUser, useUserProfile } from "@/hooks/store";
|
||||||
|
// wrappers
|
||||||
|
import { AuthWrapper } from "@/lib/wrappers";
|
||||||
// assets
|
// assets
|
||||||
import ProfileSetupDark from "public/onboarding/profile-setup-dark.svg";
|
import ProfileSetupDark from "public/onboarding/profile-setup-dark.svg";
|
||||||
import ProfileSetup from "public/onboarding/profile-setup.svg";
|
import ProfileSetup from "public/onboarding/profile-setup.svg";
|
||||||
@ -49,71 +53,78 @@ const OnBoardingPage = observer(() => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full">
|
<AuthWrapper pageType={EPageTypes.ONBOARDING}>
|
||||||
<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 h-full w-full">
|
||||||
<div className="flex items-center justify-between">
|
<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 w-full items-center justify-between font-semibold ">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-x-2">
|
<div className="flex w-full items-center justify-between font-semibold ">
|
||||||
<Image src={`${imagePrefix}/plane-logos/blue-without-text.png`} height={30} width={30} alt="Plane Logo" />
|
<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>
|
<div className="shrink-0 lg:hidden">
|
||||||
<div className="shrink-0 lg:hidden">
|
<div className="flex w-full shrink-0 justify-end">
|
||||||
<div className="flex w-full shrink-0 justify-end">
|
<div className="flex items-center gap-x-2 pr-4">
|
||||||
<div className="flex items-center gap-x-2 pr-4">
|
{user?.avatar && (
|
||||||
{user?.avatar && (
|
<Avatar
|
||||||
<Avatar
|
name={user?.first_name ? `${user?.first_name} ${user?.last_name ?? ""}` : user?.email}
|
||||||
name={user?.first_name ? `${user?.first_name} ${user?.last_name ?? ""}` : user?.email}
|
src={user?.avatar}
|
||||||
src={user?.avatar}
|
size={24}
|
||||||
size={24}
|
shape="square"
|
||||||
shape="square"
|
fallbackBackgroundColor="#FCBE1D"
|
||||||
fallbackBackgroundColor="#FCBE1D"
|
className="!text-base capitalize"
|
||||||
className="!text-base capitalize"
|
/>
|
||||||
/>
|
)}
|
||||||
)}
|
<span className="text-sm font-medium text-custom-text-200">
|
||||||
<span className="text-sm font-medium text-custom-text-200">
|
{user?.first_name ? `${user?.first_name} ${user?.last_name ?? ""}` : user?.email}
|
||||||
{user?.first_name ? `${user?.first_name} ${user?.last_name ?? ""}` : user?.email}
|
</span>
|
||||||
</span>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="flex flex-col w-full items-center justify-center p-8 mt-14">
|
||||||
<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">
|
||||||
<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>
|
||||||
<h3 className="text-3xl font-bold text-onboarding-text-100">Welcome to Plane!</h3>
|
<p className="font-medium text-onboarding-text-400">
|
||||||
<p className="font-medium text-onboarding-text-400">
|
Let’s setup your profile, tell us a bit about yourself.
|
||||||
Let’s setup your profile, tell us a bit about yourself.
|
</p>
|
||||||
</p>
|
</div>
|
||||||
</div>
|
<OnBoardingForm user={user} finishOnboarding={finishOnboarding} />
|
||||||
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute inset-0 z-0">
|
<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">
|
||||||
<Image
|
<div className="flex w-full shrink-0 justify-end">
|
||||||
src={resolvedTheme === "dark" ? ProfileSetupDark : ProfileSetup}
|
<div className="flex items-center gap-x-2 pr-4 z-10">
|
||||||
className="h-screen w-auto float-end object-cover"
|
{user?.avatar && (
|
||||||
alt="Profile setup"
|
<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>
|
</div>
|
||||||
</div>
|
</AuthWrapper>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,32 +1,39 @@
|
|||||||
// next imports
|
// next imports
|
||||||
import Image from "next/image";
|
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 = () => (
|
const CustomProjectNotPublishedError = () => (
|
||||||
<div className="relative flex h-full min-h-screen w-screen items-center justify-center py-5">
|
<AuthWrapper pageType={EPageTypes.PUBLIC}>
|
||||||
<div className="max-w-[700px] space-y-5">
|
<div className="relative flex h-full min-h-screen w-screen items-center justify-center py-5">
|
||||||
<div className="flex flex-col items-center gap-3 text-center">
|
<div className="max-w-[700px] space-y-5">
|
||||||
<div className="relative h-[240px] w-[240px]">
|
<div className="flex flex-col items-center gap-3 text-center">
|
||||||
<Image src={projectNotPublishedImage} layout="fill" alt="404- Page not found" />
|
<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>
|
||||||
<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">
|
<div className="flex items-center justify-center text-center">
|
||||||
<a
|
<a
|
||||||
href={`https://app.plane.so/`}
|
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"
|
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
|
Go to your Workspace
|
||||||
</a>
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</AuthWrapper>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default CustomProjectNotPublishedError;
|
export default CustomProjectNotPublishedError;
|
||||||
|
@ -19,7 +19,7 @@ abstract class APIService {
|
|||||||
this.axiosInstance.interceptors.response.use(
|
this.axiosInstance.interceptors.response.use(
|
||||||
(response) => response,
|
(response) => response,
|
||||||
(error) => {
|
(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);
|
return Promise.reject(error.response?.data ?? error);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -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)
|
return this.post(`/auth/forgot-password/`, data)
|
||||||
.then((response) => response?.data)
|
.then((response) => response?.data)
|
||||||
.catch((error) => {
|
.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: {} })
|
return this.post("/auth/spaces/magic-generate/", data, { headers: {} })
|
||||||
.then((response) => response?.data)
|
.then((response) => response?.data)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
@ -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)
|
return this.patch("/api/users/me/", data)
|
||||||
.then((response) => response?.data)
|
.then((response) => response?.data)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@ -33,7 +33,7 @@ export class UserService extends APIService {
|
|||||||
throw error?.response;
|
throw error?.response;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async updateCurrentUserProfile(data: any): Promise<any> {
|
async updateCurrentUserProfile(data: Partial<TUserProfile>): Promise<TUserProfile> {
|
||||||
return this.patch("/api/users/me/profile/", data)
|
return this.patch("/api/users/me/profile/", data)
|
||||||
.then((response) => response?.data)
|
.then((response) => response?.data)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { FC, useEffect, useState } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { IEmailCheckData } from "@plane/types";
|
import { IEmailCheckData } from "@plane/types";
|
||||||
@ -29,26 +29,42 @@ import { AuthService } from "@/services/auth.service";
|
|||||||
|
|
||||||
const authService = new AuthService();
|
const authService = new AuthService();
|
||||||
|
|
||||||
export const SignInAuthRoot = observer(() => {
|
type TAuthRoot = {
|
||||||
|
authMode: EAuthModes;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AuthRoot: FC<TAuthRoot> = observer((props) => {
|
||||||
//router
|
//router
|
||||||
const router = useRouter();
|
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
|
// states
|
||||||
const [authStep, setAuthStep] = useState<EAuthSteps>(EAuthSteps.EMAIL);
|
const [authStep, setAuthStep] = useState<EAuthSteps>(EAuthSteps.EMAIL);
|
||||||
const [email, setEmail] = useState(emailParam ? emailParam.toString() : "");
|
const [email, setEmail] = useState(emailParam ? emailParam.toString() : "");
|
||||||
const [errorInfo, setErrorInfo] = useState<TAuthErrorInfo | undefined>(undefined);
|
const [errorInfo, setErrorInfo] = useState<TAuthErrorInfo | undefined>(undefined);
|
||||||
// hooks
|
// hooks
|
||||||
const { instance } = useInstance();
|
const { instance } = useInstance();
|
||||||
// derived values
|
|
||||||
const authMode = EAuthModes.SIGN_IN;
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (error_code && error_message) {
|
if (error_code) {
|
||||||
const errorhandler = authErrorHandler(
|
const errorhandler = authErrorHandler(error_code?.toString() as EAuthenticationErrorCodes);
|
||||||
error_code?.toString() as EAuthenticationErrorCodes,
|
|
||||||
error_message?.toString()
|
|
||||||
);
|
|
||||||
if (errorhandler) {
|
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) {
|
if (errorhandler?.type === EErrorAlertType.TOAST_ALERT) {
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.ERROR,
|
type: TOAST_TYPE.ERROR,
|
||||||
@ -58,23 +74,30 @@ export const SignInAuthRoot = observer(() => {
|
|||||||
} else setErrorInfo(errorhandler);
|
} else setErrorInfo(errorhandler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [error_code, error_message]);
|
}, [error_code, authMode]);
|
||||||
|
|
||||||
// step 1 submit handler- email verification
|
// step 1 submit handler- email verification
|
||||||
const handleEmailVerification = async (data: IEmailCheckData) => {
|
const handleEmailVerification = async (data: IEmailCheckData) => {
|
||||||
setEmail(data.email);
|
setEmail(data.email);
|
||||||
|
|
||||||
await authService
|
const emailCheckRequest =
|
||||||
.signInEmailCheck(data)
|
authMode === EAuthModes.SIGN_IN ? authService.signInEmailCheck(data) : authService.signUpEmailCheck(data);
|
||||||
|
|
||||||
|
await emailCheckRequest
|
||||||
.then(() => {
|
.then(() => {
|
||||||
setAuthStep(EAuthSteps.PASSWORD);
|
setAuthStep(EAuthSteps.PASSWORD);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((error) => {
|
||||||
setToast({
|
const errorhandler = authErrorHandler(error?.error_code.toString());
|
||||||
type: TOAST_TYPE.ERROR,
|
if (errorhandler?.type === EErrorAlertType.BANNER_ALERT) {
|
||||||
title: "Error!",
|
setErrorInfo(errorhandler);
|
||||||
message: err?.error_message ?? "Something went wrong. Please try again.",
|
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}
|
workspaceSlug={workspaceSlug?.toString() || undefined}
|
||||||
invitationId={invitation_id?.toString() || undefined}
|
invitationId={invitation_id?.toString() || undefined}
|
||||||
invitationEmail={email || undefined}
|
invitationEmail={email || undefined}
|
||||||
authMode={EAuthModes.SIGN_IN}
|
authMode={authMode}
|
||||||
currentAuthStep={authStep}
|
currentAuthStep={authStep}
|
||||||
>
|
>
|
||||||
{errorInfo && errorInfo?.type === EErrorAlertType.BANNER_ALERT && (
|
{errorInfo && errorInfo?.type === EErrorAlertType.BANNER_ALERT && (
|
@ -1,5 +1,4 @@
|
|||||||
export * from "./sign-up-root";
|
export * from "./auth-root";
|
||||||
export * from "./sign-in-root";
|
|
||||||
|
|
||||||
export * from "./auth-header";
|
export * from "./auth-header";
|
||||||
export * from "./auth-banner";
|
export * from "./auth-banner";
|
||||||
|
@ -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>
|
|
||||||
);
|
|
||||||
});
|
|
@ -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;
|
|
||||||
};
|
|
290
web/helpers/authentication.helper.tsx
Normal file
290
web/helpers/authentication.helper.tsx
Normal 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.
|
||||||
|
<Link
|
||||||
|
className="underline underline-offset-4 font-medium hover:font-bold transition-all"
|
||||||
|
href={`/accounts/sign-in`}
|
||||||
|
>
|
||||||
|
Sign In
|
||||||
|
</Link>
|
||||||
|
now.
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
[EAuthenticationErrorCodes.USER_DOES_NOT_EXIST]: {
|
||||||
|
title: `User does not exist`,
|
||||||
|
message: (
|
||||||
|
<div>
|
||||||
|
No account found.
|
||||||
|
<Link className="underline underline-offset-4 font-medium hover:font-bold transition-all" href={`/`}>
|
||||||
|
Create one
|
||||||
|
</Link>
|
||||||
|
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;
|
||||||
|
};
|
@ -30,12 +30,7 @@ export const InstanceWrapper: FC<TInstanceWrapper> = observer((props) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// something went wrong while in the request
|
// something went wrong while in the request
|
||||||
if (error && error?.status === "error")
|
if (error && error?.status === "error") return <>{children}</>;
|
||||||
return (
|
|
||||||
<div className="relative flex h-screen w-screen items-center justify-center">
|
|
||||||
Something went wrong. please try again later
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
// instance is not ready and setup is not done
|
// instance is not ready and setup is not done
|
||||||
if (instance?.instance?.is_setup_done === false) return <InstanceNotReady />;
|
if (instance?.instance?.is_setup_done === false) return <InstanceNotReady />;
|
||||||
|
@ -122,7 +122,7 @@ const ForgotPasswordPage: NextPageWithLayout = () => {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</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="relative flex flex-col space-y-6">
|
||||||
<div className="text-center space-y-1 py-4">
|
<div className="text-center space-y-1 py-4">
|
||||||
<h3 className="flex gap-4 justify-center text-3xl font-bold text-onboarding-text-100">
|
<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"}
|
{resendTimerCode > 0 ? `Resend in ${resendTimerCode} seconds` : "Send reset link"}
|
||||||
</Button>
|
</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
|
Back to sign in
|
||||||
</Link>
|
</Link>
|
||||||
</form>
|
</form>
|
||||||
|
@ -90,7 +90,7 @@ const ResetPasswordPage: NextPageWithLayout = () => {
|
|||||||
<span className="text-2xl font-semibold sm:text-3xl">Plane</span>
|
<span className="text-2xl font-semibold sm:text-3xl">Plane</span>
|
||||||
</div>
|
</div>
|
||||||
</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="relative flex flex-col space-y-6">
|
||||||
<div className="text-center space-y-1 py-4">
|
<div className="text-center space-y-1 py-4">
|
||||||
<h3 className="flex gap-4 justify-center text-3xl font-bold text-onboarding-text-100">
|
<h3 className="flex gap-4 justify-center text-3xl font-bold text-onboarding-text-100">
|
||||||
|
@ -111,7 +111,7 @@ const SetPasswordPage: NextPageWithLayout = observer(() => {
|
|||||||
<span className="text-2xl font-semibold sm:text-3xl">Plane</span>
|
<span className="text-2xl font-semibold sm:text-3xl">Plane</span>
|
||||||
</div>
|
</div>
|
||||||
</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="relative flex flex-col space-y-6">
|
||||||
<div className="text-center space-y-1 py-4">
|
<div className="text-center space-y-1 py-4">
|
||||||
<h3 className="flex gap-4 justify-center text-3xl font-bold text-onboarding-text-100">
|
<h3 className="flex gap-4 justify-center text-3xl font-bold text-onboarding-text-100">
|
||||||
|
@ -4,12 +4,12 @@ import Link from "next/link";
|
|||||||
// ui
|
// ui
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
// components
|
// components
|
||||||
import { SignInAuthRoot } from "@/components/account";
|
import { AuthRoot } from "@/components/account";
|
||||||
import { PageHead } from "@/components/core";
|
import { PageHead } from "@/components/core";
|
||||||
// constants
|
// constants
|
||||||
import { NAVIGATE_TO_SIGNUP } from "@/constants/event-tracker";
|
import { NAVIGATE_TO_SIGNUP } from "@/constants/event-tracker";
|
||||||
// helpers
|
// helpers
|
||||||
import { EPageTypes } from "@/helpers/authentication.helper";
|
import { EAuthModes, EPageTypes } from "@/helpers/authentication.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker } from "@/hooks/store";
|
import { useEventTracker } from "@/hooks/store";
|
||||||
// layouts
|
// layouts
|
||||||
@ -58,8 +58,8 @@ const SignInPage: NextPageWithLayout = observer(() => {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
||||||
<SignInAuthRoot />
|
<AuthRoot authMode={EAuthModes.SIGN_IN} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -5,12 +5,12 @@ import Link from "next/link";
|
|||||||
// ui
|
// ui
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
// components
|
// components
|
||||||
import { SignUpAuthRoot } from "@/components/account";
|
import { AuthRoot } from "@/components/account";
|
||||||
import { PageHead } from "@/components/core";
|
import { PageHead } from "@/components/core";
|
||||||
// constants
|
// constants
|
||||||
import { NAVIGATE_TO_SIGNIN } from "@/constants/event-tracker";
|
import { NAVIGATE_TO_SIGNIN } from "@/constants/event-tracker";
|
||||||
// helpers
|
// helpers
|
||||||
import { EPageTypes } from "@/helpers/authentication.helper";
|
import { EAuthModes, EPageTypes } from "@/helpers/authentication.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker } from "@/hooks/store";
|
import { useEventTracker } from "@/hooks/store";
|
||||||
// layouts
|
// layouts
|
||||||
@ -56,8 +56,8 @@ const HomePage: NextPageWithLayout = observer(() => {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
||||||
<SignUpAuthRoot />
|
<AuthRoot authMode={EAuthModes.SIGN_UP} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user