diff --git a/admin/app/authentication/github/form.tsx b/admin/app/authentication/github/form.tsx index 23ba9ccd155..4842675cd59 100644 --- a/admin/app/authentication/github/form.tsx +++ b/admin/app/authentication/github/form.tsx @@ -4,10 +4,11 @@ import { FC, useState } from "react"; import isEmpty from "lodash/isEmpty"; import Link from "next/link"; import { useForm } from "react-hook-form"; -// types +// plane internal packages +import { API_BASE_URL } from "@plane/constants"; import { IFormattedInstanceConfiguration, TInstanceGithubAuthenticationConfigurationKeys } from "@plane/types"; -// ui import { Button, TOAST_TYPE, getButtonStyling, setToast } from "@plane/ui"; +import { cn } from "@plane/utils"; // components import { CodeBlock, @@ -17,8 +18,6 @@ import { TControllerInputFormField, TCopyField, } from "@/components/common"; -// helpers -import { API_BASE_URL, cn } from "@/helpers/common.helper"; // hooks import { useInstance } from "@/hooks/store"; @@ -103,8 +102,7 @@ export const InstanceGithubConfigForm: FC = (props) => { url: originURL, description: ( <> - We will auto-generate this. Paste this into the{" "} - Authorized origin URL field{" "} + We will auto-generate this. Paste this into the Authorized origin URL field{" "} = (props) => { url: `${originURL}/auth/github/callback/`, description: ( <> - We will auto-generate this. Paste this into your{" "} - Authorized Callback URI field{" "} + We will auto-generate this. Paste this into your Authorized Callback URI{" "} + field{" "} = (props) => { url: `${originURL}/auth/gitlab/callback/`, description: ( <> - We will auto-generate this. Paste this into the{" "} - Redirect URI field of your{" "} + We will auto-generate this. Paste this into the Redirect URI field of your{" "} { }; export default function RootLayout({ children }: { children: ReactNode }) { + const ASSET_PREFIX = ADMIN_BASE_PATH; return ( @@ -34,7 +35,7 @@ export default function RootLayout({ children }: { children: ReactNode }) { - + {children} diff --git a/admin/app/workspace/create/form.tsx b/admin/app/workspace/create/form.tsx index 958f53153a2..2a7eda207ef 100644 --- a/admin/app/workspace/create/form.tsx +++ b/admin/app/workspace/create/form.tsx @@ -3,13 +3,11 @@ import Link from "next/link"; import { useRouter } from "next/navigation"; import { Controller, useForm } from "react-hook-form"; // constants -import { ORGANIZATION_SIZE, RESTRICTED_URLS } from "@plane/constants"; +import { WEB_BASE_URL, ORGANIZATION_SIZE, RESTRICTED_URLS } from "@plane/constants"; // types import { IWorkspace } from "@plane/types"; // components import { Button, CustomSelect, getButtonStyling, Input, setToast, TOAST_TYPE } from "@plane/ui"; -// helpers -import { WEB_BASE_URL } from "@/helpers/common.helper"; // hooks import { useWorkspace } from "@/hooks/store"; // services diff --git a/admin/app/workspace/page.tsx b/admin/app/workspace/page.tsx index ef8a3c42d28..3ca34b69e39 100644 --- a/admin/app/workspace/page.tsx +++ b/admin/app/workspace/page.tsx @@ -7,12 +7,10 @@ import useSWR from "swr"; import { Loader as LoaderIcon } from "lucide-react"; // types import { TInstanceConfigurationKeys } from "@plane/types"; -// ui import { Button, getButtonStyling, Loader, setPromiseToast, ToggleSwitch } from "@plane/ui"; +import { cn } from "@plane/utils"; // components import { WorkspaceListItem } from "@/components/workspace"; -// helpers -import { cn } from "@/helpers/common.helper"; // hooks import { useInstance, useWorkspace } from "@/hooks/store"; diff --git a/admin/ce/components/authentication/authentication-modes.tsx b/admin/ce/components/authentication/authentication-modes.tsx index 84cde94d44e..3c7ec111a33 100644 --- a/admin/ce/components/authentication/authentication-modes.tsx +++ b/admin/ce/components/authentication/authentication-modes.tsx @@ -10,7 +10,7 @@ import { // components import { AuthenticationMethodCard } from "@/components/authentication"; // helpers -import { getBaseAuthenticationModes } from "@/helpers/authentication.helper"; +import { getBaseAuthenticationModes } from "@/lib/auth-helpers"; // plane admin components import { UpgradeButton } from "@/plane-admin/components/common"; // images diff --git a/admin/ce/components/common/upgrade-button.tsx b/admin/ce/components/common/upgrade-button.tsx index c2b264baeb9..208225e0cec 100644 --- a/admin/ce/components/common/upgrade-button.tsx +++ b/admin/ce/components/common/upgrade-button.tsx @@ -3,10 +3,9 @@ import React from "react"; // icons import { SquareArrowOutUpRight } from "lucide-react"; -// ui +// plane internal packages import { getButtonStyling } from "@plane/ui"; -// helpers -import { cn } from "@/helpers/common.helper"; +import { cn } from "@plane/utils"; export const UpgradeButton: React.FC = () => ( diff --git a/admin/core/components/admin-sidebar/help-section.tsx b/admin/core/components/admin-sidebar/help-section.tsx index 10d5cbd0dad..d776477497b 100644 --- a/admin/core/components/admin-sidebar/help-section.tsx +++ b/admin/core/components/admin-sidebar/help-section.tsx @@ -5,13 +5,14 @@ import { observer } from "mobx-react"; import Link from "next/link"; import { ExternalLink, FileText, HelpCircle, MoveLeft } from "lucide-react"; import { Transition } from "@headlessui/react"; -// ui +// plane internal packages +import { WEB_BASE_URL } from "@plane/constants"; import { DiscordIcon, GithubIcon, Tooltip } from "@plane/ui"; -// helpers -import { WEB_BASE_URL, cn } from "@/helpers/common.helper"; +import { cn } from "@plane/utils"; // hooks import { useTheme } from "@/hooks/store"; // assets +// eslint-disable-next-line import/order import packageJson from "package.json"; const helpOptions = [ diff --git a/admin/core/components/admin-sidebar/sidebar-dropdown.tsx b/admin/core/components/admin-sidebar/sidebar-dropdown.tsx index e0741f7c4a4..f34372413fe 100644 --- a/admin/core/components/admin-sidebar/sidebar-dropdown.tsx +++ b/admin/core/components/admin-sidebar/sidebar-dropdown.tsx @@ -5,11 +5,10 @@ import { observer } from "mobx-react"; import { useTheme as useNextTheme } from "next-themes"; import { LogOut, UserCog2, Palette } from "lucide-react"; import { Menu, Transition } from "@headlessui/react"; -// plane ui +// plane internal packages +import { API_BASE_URL } from "@plane/constants"; import { Avatar } from "@plane/ui"; -// helpers -import { API_BASE_URL, cn } from "@/helpers/common.helper"; -import { getFileURL } from "@/helpers/file.helper"; +import { getFileURL, cn } from "@plane/utils"; // hooks import { useTheme, useUser } from "@/hooks/store"; // services diff --git a/admin/core/components/admin-sidebar/sidebar-menu.tsx b/admin/core/components/admin-sidebar/sidebar-menu.tsx index a985842e7f3..618551ae65c 100644 --- a/admin/core/components/admin-sidebar/sidebar-menu.tsx +++ b/admin/core/components/admin-sidebar/sidebar-menu.tsx @@ -4,11 +4,11 @@ import { observer } from "mobx-react"; import Link from "next/link"; import { usePathname } from "next/navigation"; import { Image, BrainCog, Cog, Lock, Mail } from "lucide-react"; +// plane internal packages import { Tooltip, WorkspaceIcon } from "@plane/ui"; +import { cn } from "@plane/utils"; // hooks -import { cn } from "@/helpers/common.helper"; import { useTheme } from "@/hooks/store"; -// helpers const INSTANCE_ADMIN_LINKS = [ { diff --git a/admin/core/components/authentication/auth-banner.tsx b/admin/core/components/authentication/auth-banner.tsx index 191d7a0a772..7c1e5ea292b 100644 --- a/admin/core/components/authentication/auth-banner.tsx +++ b/admin/core/components/authentication/auth-banner.tsx @@ -1,7 +1,7 @@ import { FC } from "react"; import { Info, X } from "lucide-react"; -// helpers -import { TAuthErrorInfo } from "@/helpers/authentication.helper"; +// plane constants +import { TAuthErrorInfo } from "@plane/constants"; type TAuthBanner = { bannerData: TAuthErrorInfo | undefined; diff --git a/admin/core/components/authentication/authentication-method-card.tsx b/admin/core/components/authentication/authentication-method-card.tsx index 50895a45920..897deb7c481 100644 --- a/admin/core/components/authentication/authentication-method-card.tsx +++ b/admin/core/components/authentication/authentication-method-card.tsx @@ -2,7 +2,7 @@ import { FC } from "react"; // helpers -import { cn } from "helpers/common.helper"; +import { cn } from "@plane/utils"; type Props = { name: string; diff --git a/admin/core/components/authentication/github-config.tsx b/admin/core/components/authentication/github-config.tsx index 07c566d684e..57035580f66 100644 --- a/admin/core/components/authentication/github-config.tsx +++ b/admin/core/components/authentication/github-config.tsx @@ -5,12 +5,10 @@ import { observer } from "mobx-react"; import Link from "next/link"; // icons import { Settings2 } from "lucide-react"; -// types +// plane internal packages import { TInstanceAuthenticationMethodKeys } from "@plane/types"; -// ui import { ToggleSwitch, getButtonStyling } from "@plane/ui"; -// helpers -import { cn } from "@/helpers/common.helper"; +import { cn } from "@plane/utils"; // hooks import { useInstance } from "@/hooks/store"; diff --git a/admin/core/components/authentication/gitlab-config.tsx b/admin/core/components/authentication/gitlab-config.tsx index 735201025bc..4181338d21e 100644 --- a/admin/core/components/authentication/gitlab-config.tsx +++ b/admin/core/components/authentication/gitlab-config.tsx @@ -5,12 +5,10 @@ import { observer } from "mobx-react"; import Link from "next/link"; // icons import { Settings2 } from "lucide-react"; -// types +// plane internal packages import { TInstanceAuthenticationMethodKeys } from "@plane/types"; -// ui import { ToggleSwitch, getButtonStyling } from "@plane/ui"; -// helpers -import { cn } from "@/helpers/common.helper"; +import { cn } from "@plane/utils"; // hooks import { useInstance } from "@/hooks/store"; diff --git a/admin/core/components/authentication/google-config.tsx b/admin/core/components/authentication/google-config.tsx index 12d11a2f89b..0f3cc98e386 100644 --- a/admin/core/components/authentication/google-config.tsx +++ b/admin/core/components/authentication/google-config.tsx @@ -5,12 +5,10 @@ import { observer } from "mobx-react"; import Link from "next/link"; // icons import { Settings2 } from "lucide-react"; -// types +// plane internal packages import { TInstanceAuthenticationMethodKeys } from "@plane/types"; -// ui import { ToggleSwitch, getButtonStyling } from "@plane/ui"; -// helpers -import { cn } from "@/helpers/common.helper"; +import { cn } from "@plane/utils"; // hooks import { useInstance } from "@/hooks/store"; diff --git a/admin/core/components/common/code-block.tsx b/admin/core/components/common/code-block.tsx index 55f8b4afb45..88ad78a1e16 100644 --- a/admin/core/components/common/code-block.tsx +++ b/admin/core/components/common/code-block.tsx @@ -1,4 +1,4 @@ -import { cn } from "@/helpers/common.helper"; +import { cn } from "@plane/utils"; type TProps = { children: React.ReactNode; diff --git a/admin/core/components/common/controller-input.tsx b/admin/core/components/common/controller-input.tsx index 4d0eade08ed..ca8f301620c 100644 --- a/admin/core/components/common/controller-input.tsx +++ b/admin/core/components/common/controller-input.tsx @@ -4,10 +4,9 @@ import React, { useState } from "react"; import { Controller, Control } from "react-hook-form"; // icons import { Eye, EyeOff } from "lucide-react"; -// ui +// plane internal packages import { Input } from "@plane/ui"; -// helpers -import { cn } from "@/helpers/common.helper"; +import { cn } from "@plane/utils"; type Props = { control: Control; @@ -37,9 +36,7 @@ export const ControllerInput: React.FC = (props) => { return (
-

- {label} -

+

{label}

{ useEffect(() => { if (errorCode) { - const errorDetail = authErrorHandler(errorCode?.toString() as EAuthenticationErrorCodes); + const errorDetail = authErrorHandler(errorCode?.toString() as EAdminAuthErrorCodes); if (errorDetail) { setErrorInfo(errorDetail); } diff --git a/admin/core/components/new-user-popup.tsx b/admin/core/components/new-user-popup.tsx index 8e1570781c4..0b974b38c5d 100644 --- a/admin/core/components/new-user-popup.tsx +++ b/admin/core/components/new-user-popup.tsx @@ -1,13 +1,13 @@ "use client"; import React from "react"; -import { resolveGeneralTheme } from "helpers/common.helper"; import { observer } from "mobx-react"; import Image from "next/image"; import Link from "next/link"; import { useTheme as nextUseTheme } from "next-themes"; // ui import { Button, getButtonStyling } from "@plane/ui"; +import { resolveGeneralTheme } from "@plane/utils"; // hooks import { useTheme } from "@/hooks/store"; // icons diff --git a/admin/core/components/workspace/list-item.tsx b/admin/core/components/workspace/list-item.tsx index e0a96a6ef68..ae693eb728c 100644 --- a/admin/core/components/workspace/list-item.tsx +++ b/admin/core/components/workspace/list-item.tsx @@ -1,9 +1,9 @@ import { observer } from "mobx-react"; import { ExternalLink } from "lucide-react"; -// helpers +// plane internal packages +import { WEB_BASE_URL } from "@plane/constants"; import { Tooltip } from "@plane/ui"; -import { WEB_BASE_URL } from "@/helpers/common.helper"; -import { getFileURL } from "@/helpers/file.helper"; +import { getFileURL } from "@plane/utils"; // hooks import { useWorkspace } from "@/hooks/store"; diff --git a/admin/core/lib/auth-helpers.tsx b/admin/core/lib/auth-helpers.tsx new file mode 100644 index 00000000000..582b56e298c --- /dev/null +++ b/admin/core/lib/auth-helpers.tsx @@ -0,0 +1,164 @@ +import { ReactNode } from "react"; +import Image from "next/image"; +import Link from "next/link"; +import { KeyRound, Mails } from "lucide-react"; +// plane packages +import { SUPPORT_EMAIL, EAdminAuthErrorCodes, TAuthErrorInfo } from "@plane/constants"; +import { TGetBaseAuthenticationModeProps, TInstanceAuthenticationModes } from "@plane/types"; +import { resolveGeneralTheme } from "@plane/utils"; +// components +import { + EmailCodesConfiguration, + GithubConfiguration, + GitlabConfiguration, + GoogleConfiguration, + PasswordLoginConfiguration, +} from "@/components/authentication"; +// images +import githubLightModeImage from "@/public/logos/github-black.png"; +import githubDarkModeImage from "@/public/logos/github-white.png"; +import GitlabLogo from "@/public/logos/gitlab-logo.svg"; +import GoogleLogo from "@/public/logos/google-logo.svg"; + +export enum EErrorAlertType { + BANNER_ALERT = "BANNER_ALERT", + INLINE_FIRST_NAME = "INLINE_FIRST_NAME", + INLINE_EMAIL = "INLINE_EMAIL", + INLINE_PASSWORD = "INLINE_PASSWORD", + INLINE_EMAIL_CODE = "INLINE_EMAIL_CODE", +} + +const errorCodeMessages: { + [key in EAdminAuthErrorCodes]: { title: string; message: (email?: string | undefined) => ReactNode }; +} = { + // admin + [EAdminAuthErrorCodes.ADMIN_ALREADY_EXIST]: { + title: `Admin already exists`, + message: () => `Admin already exists. Please try again.`, + }, + [EAdminAuthErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME]: { + title: `Email, password and first name required`, + message: () => `Email, password and first name required. Please try again.`, + }, + [EAdminAuthErrorCodes.INVALID_ADMIN_EMAIL]: { + title: `Invalid admin email`, + message: () => `Invalid admin email. Please try again.`, + }, + [EAdminAuthErrorCodes.INVALID_ADMIN_PASSWORD]: { + title: `Invalid admin password`, + message: () => `Invalid admin password. Please try again.`, + }, + [EAdminAuthErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD]: { + title: `Email and password required`, + message: () => `Email and password required. Please try again.`, + }, + [EAdminAuthErrorCodes.ADMIN_AUTHENTICATION_FAILED]: { + title: `Authentication failed`, + message: () => `Authentication failed. Please try again.`, + }, + [EAdminAuthErrorCodes.ADMIN_USER_ALREADY_EXIST]: { + title: `Admin user already exists`, + message: () => ( +
+ Admin user already exists.  + + Sign In + +  now. +
+ ), + }, + [EAdminAuthErrorCodes.ADMIN_USER_DOES_NOT_EXIST]: { + title: `Admin user does not exist`, + message: () => ( +
+ Admin user does not exist.  + + Sign In + +  now. +
+ ), + }, + [EAdminAuthErrorCodes.ADMIN_USER_DEACTIVATED]: { + title: `User account deactivated`, + message: () => `User account deactivated. Please contact ${!!SUPPORT_EMAIL ? SUPPORT_EMAIL : "administrator"}.`, + }, +}; + +export const authErrorHandler = ( + errorCode: EAdminAuthErrorCodes, + email?: string | undefined +): TAuthErrorInfo | undefined => { + const bannerAlertErrorCodes = [ + EAdminAuthErrorCodes.ADMIN_ALREADY_EXIST, + EAdminAuthErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME, + EAdminAuthErrorCodes.INVALID_ADMIN_EMAIL, + EAdminAuthErrorCodes.INVALID_ADMIN_PASSWORD, + EAdminAuthErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD, + EAdminAuthErrorCodes.ADMIN_AUTHENTICATION_FAILED, + EAdminAuthErrorCodes.ADMIN_USER_ALREADY_EXIST, + EAdminAuthErrorCodes.ADMIN_USER_DOES_NOT_EXIST, + EAdminAuthErrorCodes.ADMIN_USER_DEACTIVATED, + ]; + + if (bannerAlertErrorCodes.includes(errorCode)) + return { + type: EErrorAlertType.BANNER_ALERT, + code: errorCode, + title: errorCodeMessages[errorCode]?.title || "Error", + message: errorCodeMessages[errorCode]?.message(email) || "Something went wrong. Please try again.", + }; + + return undefined; +}; + +export const getBaseAuthenticationModes: (props: TGetBaseAuthenticationModeProps) => TInstanceAuthenticationModes[] = ({ + disabled, + updateConfig, + resolvedTheme, +}) => [ + { + key: "unique-codes", + name: "Unique codes", + description: + "Log in or sign up for Plane using codes sent via email. You need to have set up SMTP to use this method.", + icon: , + config: , + }, + { + key: "passwords-login", + name: "Passwords", + description: "Allow members to create accounts with passwords and use it with their email addresses to sign in.", + icon: , + config: , + }, + { + key: "google", + name: "Google", + description: "Allow members to log in or sign up for Plane with their Google accounts.", + icon: Google Logo, + config: , + }, + { + key: "github", + name: "GitHub", + description: "Allow members to log in or sign up for Plane with their GitHub accounts.", + icon: ( + GitHub Logo + ), + config: , + }, + { + key: "gitlab", + name: "GitLab", + description: "Allow members to log in or sign up to plane with their GitLab accounts.", + icon: GitLab Logo, + config: , + }, +]; diff --git a/admin/core/services/auth.service.ts b/admin/core/services/auth.service.ts index 2cea01beec6..a47fd839628 100644 --- a/admin/core/services/auth.service.ts +++ b/admin/core/services/auth.service.ts @@ -1,5 +1,4 @@ -// helpers -import { API_BASE_URL } from "@/helpers/common.helper"; +import { API_BASE_URL } from "@plane/constants"; // services import { APIService } from "@/services/api.service"; diff --git a/admin/core/services/instance.service.ts b/admin/core/services/instance.service.ts index feb94ceea45..510a780d792 100644 --- a/admin/core/services/instance.service.ts +++ b/admin/core/services/instance.service.ts @@ -1,4 +1,5 @@ -// types +// plane internal packages +import { API_BASE_URL } from "@plane/constants"; import type { IFormattedInstanceConfiguration, IInstance, @@ -7,7 +8,6 @@ import type { IInstanceInfo, } from "@plane/types"; // helpers -import { API_BASE_URL } from "@/helpers/common.helper"; import { APIService } from "@/services/api.service"; export class InstanceService extends APIService { diff --git a/admin/core/services/user.service.ts b/admin/core/services/user.service.ts index 42eb6eb224b..74ef2a81bfa 100644 --- a/admin/core/services/user.service.ts +++ b/admin/core/services/user.service.ts @@ -1,7 +1,6 @@ -// types +// plane internal packages +import { API_BASE_URL } from "@plane/constants"; import type { IUser } from "@plane/types"; -// helpers -import { API_BASE_URL } from "@/helpers/common.helper"; // services import { APIService } from "@/services/api.service"; diff --git a/admin/core/services/workspace.service.ts b/admin/core/services/workspace.service.ts index 81ba36a6f76..787ad426983 100644 --- a/admin/core/services/workspace.service.ts +++ b/admin/core/services/workspace.service.ts @@ -1,7 +1,6 @@ -// types +// plane internal packages +import { API_BASE_URL } from "@plane/constants"; import type { IWorkspace, TWorkspacePaginationInfo } from "@plane/types"; -// helpers -import { API_BASE_URL } from "@/helpers/common.helper"; // services import { APIService } from "@/services/api.service"; diff --git a/admin/core/store/instance.store.ts b/admin/core/store/instance.store.ts index 01ab552846f..0daadb1fcda 100644 --- a/admin/core/store/instance.store.ts +++ b/admin/core/store/instance.store.ts @@ -1,5 +1,7 @@ import set from "lodash/set"; import { observable, action, computed, makeObservable, runInAction } from "mobx"; +// plane internal packages +import { EInstanceStatus, TInstanceStatus } from "@plane/constants"; import { IInstance, IInstanceAdmin, @@ -8,8 +10,6 @@ import { IInstanceInfo, IInstanceConfig, } from "@plane/types"; -// helpers -import { EInstanceStatus, TInstanceStatus } from "@/helpers/instance.helper"; // services import { InstanceService } from "@/services/instance.service"; // root store diff --git a/admin/core/store/user.store.ts b/admin/core/store/user.store.ts index df17c9b0046..7f56c0523ef 100644 --- a/admin/core/store/user.store.ts +++ b/admin/core/store/user.store.ts @@ -1,7 +1,7 @@ import { action, observable, runInAction, makeObservable } from "mobx"; +// plane internal packages +import { EUserStatus, TUserStatus } from "@plane/constants"; import { IUser } from "@plane/types"; -// helpers -import { EUserStatus, TUserStatus } from "@/helpers/user.helper"; // services import { AuthService } from "@/services/auth.service"; import { UserService } from "@/services/user.service"; diff --git a/admin/helpers/authentication.helper.tsx b/admin/helpers/authentication.helper.tsx deleted file mode 100644 index 627ff182cad..00000000000 --- a/admin/helpers/authentication.helper.tsx +++ /dev/null @@ -1,203 +0,0 @@ -import { ReactNode } from "react"; -import Image from "next/image"; -import Link from "next/link"; -import { KeyRound, Mails } from "lucide-react"; -// types -import { TGetBaseAuthenticationModeProps, TInstanceAuthenticationModes } from "@plane/types"; -// components -import { - EmailCodesConfiguration, - GithubConfiguration, - GitlabConfiguration, - GoogleConfiguration, - PasswordLoginConfiguration, -} from "@/components/authentication"; -// helpers -import { SUPPORT_EMAIL, resolveGeneralTheme } from "@/helpers/common.helper"; -// images -import githubLightModeImage from "@/public/logos/github-black.png"; -import githubDarkModeImage from "@/public/logos/github-white.png"; -import GitlabLogo from "@/public/logos/gitlab-logo.svg"; -import GoogleLogo from "@/public/logos/google-logo.svg"; - -export enum EPageTypes { - PUBLIC = "PUBLIC", - NON_AUTHENTICATED = "NON_AUTHENTICATED", - SET_PASSWORD = "SET_PASSWORD", - 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 EErrorAlertType { - BANNER_ALERT = "BANNER_ALERT", - INLINE_FIRST_NAME = "INLINE_FIRST_NAME", - INLINE_EMAIL = "INLINE_EMAIL", - INLINE_PASSWORD = "INLINE_PASSWORD", - INLINE_EMAIL_CODE = "INLINE_EMAIL_CODE", -} - -export enum EAuthenticationErrorCodes { - // Admin - ADMIN_ALREADY_EXIST = "5150", - REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME = "5155", - INVALID_ADMIN_EMAIL = "5160", - INVALID_ADMIN_PASSWORD = "5165", - REQUIRED_ADMIN_EMAIL_PASSWORD = "5170", - ADMIN_AUTHENTICATION_FAILED = "5175", - ADMIN_USER_ALREADY_EXIST = "5180", - ADMIN_USER_DOES_NOT_EXIST = "5185", - ADMIN_USER_DEACTIVATED = "5190", -} - -export type TAuthErrorInfo = { - type: EErrorAlertType; - code: EAuthenticationErrorCodes; - title: string; - message: ReactNode; -}; - -const errorCodeMessages: { - [key in EAuthenticationErrorCodes]: { title: string; message: (email?: string | undefined) => ReactNode }; -} = { - // admin - [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 admin email`, - message: () => `Invalid admin email. Please try again.`, - }, - [EAuthenticationErrorCodes.INVALID_ADMIN_PASSWORD]: { - title: `Invalid admin password`, - message: () => `Invalid admin 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.`, - }, - [EAuthenticationErrorCodes.ADMIN_USER_ALREADY_EXIST]: { - title: `Admin user already exists`, - message: () => ( -
- Admin user already exists.  - - Sign In - -  now. -
- ), - }, - [EAuthenticationErrorCodes.ADMIN_USER_DOES_NOT_EXIST]: { - title: `Admin user does not exist`, - message: () => ( -
- Admin user does not exist.  - - Sign In - -  now. -
- ), - }, - [EAuthenticationErrorCodes.ADMIN_USER_DEACTIVATED]: { - title: `User account deactivated`, - message: () => `User account deactivated. Please contact ${!!SUPPORT_EMAIL ? SUPPORT_EMAIL : "administrator"}.`, - }, -}; - -export const authErrorHandler = ( - errorCode: EAuthenticationErrorCodes, - email?: string | undefined -): TAuthErrorInfo | undefined => { - const bannerAlertErrorCodes = [ - 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, - EAuthenticationErrorCodes.ADMIN_USER_ALREADY_EXIST, - EAuthenticationErrorCodes.ADMIN_USER_DOES_NOT_EXIST, - EAuthenticationErrorCodes.ADMIN_USER_DEACTIVATED, - ]; - - if (bannerAlertErrorCodes.includes(errorCode)) - return { - type: EErrorAlertType.BANNER_ALERT, - code: errorCode, - title: errorCodeMessages[errorCode]?.title || "Error", - message: errorCodeMessages[errorCode]?.message(email) || "Something went wrong. Please try again.", - }; - - return undefined; -}; - -export const getBaseAuthenticationModes: (props: TGetBaseAuthenticationModeProps) => TInstanceAuthenticationModes[] = ({ - disabled, - updateConfig, - resolvedTheme, -}) => [ - { - key: "unique-codes", - name: "Unique codes", - description: - "Log in or sign up for Plane using codes sent via email. You need to have set up SMTP to use this method.", - icon: , - config: , - }, - { - key: "passwords-login", - name: "Passwords", - description: "Allow members to create accounts with passwords and use it with their email addresses to sign in.", - icon: , - config: , - }, - { - key: "google", - name: "Google", - description: "Allow members to log in or sign up for Plane with their Google accounts.", - icon: Google Logo, - config: , - }, - { - key: "github", - name: "GitHub", - description: "Allow members to log in or sign up for Plane with their GitHub accounts.", - icon: ( - GitHub Logo - ), - config: , - }, - { - key: "gitlab", - name: "GitLab", - description: "Allow members to log in or sign up to plane with their GitLab accounts.", - icon: GitLab Logo, - config: , - }, - ]; diff --git a/admin/helpers/common.helper.ts b/admin/helpers/common.helper.ts deleted file mode 100644 index e282e57925f..00000000000 --- a/admin/helpers/common.helper.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { clsx, type ClassValue } from "clsx"; -import { twMerge } from "tailwind-merge"; - -export const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || ""; - -export const ADMIN_BASE_PATH = process.env.NEXT_PUBLIC_ADMIN_BASE_PATH || ""; - -export const SPACE_BASE_URL = process.env.NEXT_PUBLIC_SPACE_BASE_URL || ""; -export const SPACE_BASE_PATH = process.env.NEXT_PUBLIC_SPACE_BASE_PATH || ""; - -export const WEB_BASE_URL = process.env.NEXT_PUBLIC_WEB_BASE_URL || ""; - -export const SUPPORT_EMAIL = process.env.NEXT_PUBLIC_SUPPORT_EMAIL || ""; - -export const ASSET_PREFIX = ADMIN_BASE_PATH; - -export const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs)); - -export const resolveGeneralTheme = (resolvedTheme: string | undefined) => - resolvedTheme?.includes("light") ? "light" : resolvedTheme?.includes("dark") ? "dark" : "system"; diff --git a/admin/helpers/index.ts b/admin/helpers/index.ts deleted file mode 100644 index ae6aab829c3..00000000000 --- a/admin/helpers/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./instance.helper"; -export * from "./user.helper"; diff --git a/admin/helpers/password.helper.ts b/admin/helpers/password.helper.ts deleted file mode 100644 index dfe9a5c65fb..00000000000 --- a/admin/helpers/password.helper.ts +++ /dev/null @@ -1,67 +0,0 @@ -import zxcvbn from "zxcvbn"; - -export enum E_PASSWORD_STRENGTH { - EMPTY = "empty", - LENGTH_NOT_VALID = "length_not_valid", - STRENGTH_NOT_VALID = "strength_not_valid", - STRENGTH_VALID = "strength_valid", -} - -const PASSWORD_MIN_LENGTH = 8; -// const PASSWORD_NUMBER_REGEX = /\d/; -// const PASSWORD_CHAR_CAPS_REGEX = /[A-Z]/; -// const PASSWORD_SPECIAL_CHAR_REGEX = /[`!@#$%^&*()_\-+=\[\]{};':"\\|,.<>\/?~ ]/; - -export const PASSWORD_CRITERIA = [ - { - key: "min_8_char", - label: "Min 8 characters", - isCriteriaValid: (password: string) => password.length >= PASSWORD_MIN_LENGTH, - }, - // { - // key: "min_1_upper_case", - // label: "Min 1 upper-case letter", - // isCriteriaValid: (password: string) => PASSWORD_NUMBER_REGEX.test(password), - // }, - // { - // key: "min_1_number", - // label: "Min 1 number", - // isCriteriaValid: (password: string) => PASSWORD_CHAR_CAPS_REGEX.test(password), - // }, - // { - // key: "min_1_special_char", - // label: "Min 1 special character", - // isCriteriaValid: (password: string) => PASSWORD_SPECIAL_CHAR_REGEX.test(password), - // }, -]; - -export const getPasswordStrength = (password: string): E_PASSWORD_STRENGTH => { - let passwordStrength: E_PASSWORD_STRENGTH = E_PASSWORD_STRENGTH.EMPTY; - - if (!password || password === "" || password.length <= 0) { - return passwordStrength; - } - - if (password.length >= PASSWORD_MIN_LENGTH) { - passwordStrength = E_PASSWORD_STRENGTH.STRENGTH_NOT_VALID; - } else { - passwordStrength = E_PASSWORD_STRENGTH.LENGTH_NOT_VALID; - return passwordStrength; - } - - const passwordCriteriaValidation = PASSWORD_CRITERIA.map((criteria) => criteria.isCriteriaValid(password)).every( - (criterion) => criterion - ); - const passwordStrengthScore = zxcvbn(password).score; - - if (passwordCriteriaValidation === false || passwordStrengthScore <= 2) { - passwordStrength = E_PASSWORD_STRENGTH.STRENGTH_NOT_VALID; - return passwordStrength; - } - - if (passwordCriteriaValidation === true && passwordStrengthScore >= 3) { - passwordStrength = E_PASSWORD_STRENGTH.STRENGTH_VALID; - } - - return passwordStrength; -}; diff --git a/admin/helpers/string.helper.ts b/admin/helpers/string.helper.ts deleted file mode 100644 index a48508118e8..00000000000 --- a/admin/helpers/string.helper.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * @description - * This function test whether a URL is valid or not. - * - * It accepts URLs with or without the protocol. - * @param {string} url - * @returns {boolean} - * @example - * checkURLValidity("https://example.com") => true - * checkURLValidity("example.com") => true - * checkURLValidity("example") => false - */ -export const checkURLValidity = (url: string): boolean => { - if (!url) return false; - - // regex to support complex query parameters and fragments - const urlPattern = - /^(https?:\/\/)?((([a-z\d-]+\.)*[a-z\d-]+\.[a-z]{2,6})|(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}))(:\d+)?(\/[\w.-]*)*(\?[^#\s]*)?(#[\w-]*)?$/i; - - return urlPattern.test(url); -}; diff --git a/admin/tsconfig.json b/admin/tsconfig.json index 1748435814d..f9bb7cf10b7 100644 --- a/admin/tsconfig.json +++ b/admin/tsconfig.json @@ -5,7 +5,6 @@ "baseUrl": ".", "paths": { "@/*": ["core/*"], - "@/helpers/*": ["helpers/*"], "@/public/*": ["public/*"], "@/plane-admin/*": ["ce/*"] } diff --git a/packages/constants/src/auth.ts b/packages/constants/src/auth.ts index 59f08a37f06..8d34766d467 100644 --- a/packages/constants/src/auth.ts +++ b/packages/constants/src/auth.ts @@ -1,3 +1,36 @@ +export enum E_PASSWORD_STRENGTH { + EMPTY = "empty", + LENGTH_NOT_VALID = "length_not_valid", + STRENGTH_NOT_VALID = "strength_not_valid", + STRENGTH_VALID = "strength_valid", +} + +export const PASSWORD_MIN_LENGTH = 8; + +export const PASSWORD_CRITERIA = [ + { + key: "min_8_char", + label: "Min 8 characters", + isCriteriaValid: (password: string) => + password.length >= PASSWORD_MIN_LENGTH, + }, + // { + // key: "min_1_upper_case", + // label: "Min 1 upper-case letter", + // isCriteriaValid: (password: string) => PASSWORD_NUMBER_REGEX.test(password), + // }, + // { + // key: "min_1_number", + // label: "Min 1 number", + // isCriteriaValid: (password: string) => PASSWORD_CHAR_CAPS_REGEX.test(password), + // }, + // { + // key: "min_1_special_char", + // label: "Min 1 special character", + // isCriteriaValid: (password: string) => PASSWORD_SPECIAL_CHAR_REGEX.test(password), + // }, +]; + export enum EAuthPageTypes { PUBLIC = "PUBLIC", NON_AUTHENTICATED = "NON_AUTHENTICATED", @@ -26,6 +59,26 @@ export enum EErrorAlertType { INLINE_EMAIL_CODE = "INLINE_EMAIL_CODE", } +export type TAuthErrorInfo = { + type: EErrorAlertType; + code: EAdminAuthErrorCodes; + title: string; + message: any; +}; + +export enum EAdminAuthErrorCodes { + // Admin + ADMIN_ALREADY_EXIST = "5150", + REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME = "5155", + INVALID_ADMIN_EMAIL = "5160", + INVALID_ADMIN_PASSWORD = "5165", + REQUIRED_ADMIN_EMAIL_PASSWORD = "5170", + ADMIN_AUTHENTICATION_FAILED = "5175", + ADMIN_USER_ALREADY_EXIST = "5180", + ADMIN_USER_DOES_NOT_EXIST = "5185", + ADMIN_USER_DEACTIVATED = "5190", +} + export enum EAuthErrorCodes { // Global INSTANCE_NOT_CONFIGURED = "5000", diff --git a/packages/constants/src/endpoints.ts b/packages/constants/src/endpoints.ts index fa6db6ec73c..8f7b5046445 100644 --- a/packages/constants/src/endpoints.ts +++ b/packages/constants/src/endpoints.ts @@ -13,6 +13,14 @@ export const SITES_URL = encodeURI(`${SPACE_BASE_URL}${SPACE_BASE_PATH}/`); export const LIVE_BASE_URL = process.env.NEXT_PUBLIC_LIVE_BASE_URL || ""; export const LIVE_BASE_PATH = process.env.NEXT_PUBLIC_LIVE_BASE_PATH || ""; export const LIVE_URL = encodeURI(`${LIVE_BASE_URL}${LIVE_BASE_PATH}/`); +// Web App Base Url +export const WEB_BASE_URL = process.env.NEXT_PUBLIC_WEB_BASE_URL || ""; +export const WEB_BASE_PATH = process.env.NEXT_PUBLIC_WEB_BASE_PATH || ""; +export const WEB_URL = encodeURI(`${WEB_BASE_URL}${WEB_BASE_PATH}`); // plane website url export const WEBSITE_URL = process.env.NEXT_PUBLIC_WEBSITE_URL || "https://plane.so"; + +// support email +export const SUPPORT_EMAIL = + process.env.NEXT_PUBLIC_SUPPORT_EMAIL || "support@plane.so"; diff --git a/packages/constants/src/index.ts b/packages/constants/src/index.ts index 4189086225d..6d9d5ff9232 100644 --- a/packages/constants/src/index.ts +++ b/packages/constants/src/index.ts @@ -1,4 +1,8 @@ export * from "./auth"; export * from "./endpoints"; +export * from "./instance"; export * from "./issue"; +export * from "./metadata"; +export * from "./swr"; +export * from "./user"; export * from "./workspace"; diff --git a/admin/helpers/instance.helper.ts b/packages/constants/src/instance.ts similarity index 100% rename from admin/helpers/instance.helper.ts rename to packages/constants/src/instance.ts diff --git a/admin/core/constants/seo.ts b/packages/constants/src/metadata.ts similarity index 56% rename from admin/core/constants/seo.ts rename to packages/constants/src/metadata.ts index aafd5f7a3ed..0546c25f209 100644 --- a/admin/core/constants/seo.ts +++ b/packages/constants/src/metadata.ts @@ -1,8 +1,11 @@ -export const SITE_NAME = "Plane | Simple, extensible, open-source project management tool."; -export const SITE_TITLE = "Plane | Simple, extensible, open-source project management tool."; +export const SITE_NAME = + "Plane | Simple, extensible, open-source project management tool."; +export const SITE_TITLE = + "Plane | Simple, extensible, open-source project management tool."; export const SITE_DESCRIPTION = "Open-source project management tool to manage issues, sprints, and product roadmaps with peace of mind."; export const SITE_KEYWORDS = "software development, plan, ship, software, accelerate, code management, release management, project management, issue tracking, agile, scrum, kanban, collaboration"; export const SITE_URL = "https://app.plane.so/"; -export const TWITTER_USER_NAME = "Plane | Simple, extensible, open-source project management tool."; +export const TWITTER_USER_NAME = + "Plane | Simple, extensible, open-source project management tool."; diff --git a/admin/core/constants/swr-config.ts b/packages/constants/src/swr.ts similarity index 81% rename from admin/core/constants/swr-config.ts rename to packages/constants/src/swr.ts index 38478fcea3a..d3eef1cdfe8 100644 --- a/admin/core/constants/swr-config.ts +++ b/packages/constants/src/swr.ts @@ -1,4 +1,4 @@ -export const SWR_CONFIG = { +export const DEFAULT_SWR_CONFIG = { refreshWhenHidden: false, revalidateIfStale: false, revalidateOnFocus: false, diff --git a/admin/helpers/user.helper.ts b/packages/constants/src/user.ts similarity index 100% rename from admin/helpers/user.helper.ts rename to packages/constants/src/user.ts diff --git a/packages/utils/src/auth.ts b/packages/utils/src/auth.ts new file mode 100644 index 00000000000..2fe7ba732bc --- /dev/null +++ b/packages/utils/src/auth.ts @@ -0,0 +1,33 @@ +import zxcvbn from "zxcvbn"; +import { E_PASSWORD_STRENGTH, PASSWORD_CRITERIA, PASSWORD_MIN_LENGTH } from "@plane/constants"; + +export const getPasswordStrength = (password: string): E_PASSWORD_STRENGTH => { + let passwordStrength: E_PASSWORD_STRENGTH = E_PASSWORD_STRENGTH.EMPTY; + + if (!password || password === "" || password.length <= 0) { + return passwordStrength; + } + + if (password.length >= PASSWORD_MIN_LENGTH) { + passwordStrength = E_PASSWORD_STRENGTH.STRENGTH_NOT_VALID; + } else { + passwordStrength = E_PASSWORD_STRENGTH.LENGTH_NOT_VALID; + return passwordStrength; + } + + const passwordCriteriaValidation = PASSWORD_CRITERIA.map((criteria) => criteria.isCriteriaValid(password)).every( + (criterion) => criterion + ); + const passwordStrengthScore = zxcvbn(password).score; + + if (passwordCriteriaValidation === false || passwordStrengthScore <= 2) { + passwordStrength = E_PASSWORD_STRENGTH.STRENGTH_NOT_VALID; + return passwordStrength; + } + + if (passwordCriteriaValidation === true && passwordStrengthScore >= 3) { + passwordStrength = E_PASSWORD_STRENGTH.STRENGTH_VALID; + } + + return passwordStrength; +}; diff --git a/admin/helpers/file.helper.ts b/packages/utils/src/file.ts similarity index 83% rename from admin/helpers/file.helper.ts rename to packages/utils/src/file.ts index 6e1f546360c..6e3394abe97 100644 --- a/admin/helpers/file.helper.ts +++ b/packages/utils/src/file.ts @@ -1,5 +1,4 @@ -// helpers -import { API_BASE_URL } from "@/helpers/common.helper"; +import { API_BASE_URL } from "@plane/constants"; /** * @description combine the file path with the base URL diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index cf629534521..b897cdfc45f 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -1,4 +1,7 @@ +export * from "./auth"; export * from "./color"; export * from "./common"; export * from "./emoji"; +export * from "./file"; export * from "./string"; +export * from "./theme"; diff --git a/packages/utils/src/string.ts b/packages/utils/src/string.ts index c3c8b15418a..753231b923c 100644 --- a/packages/utils/src/string.ts +++ b/packages/utils/src/string.ts @@ -13,3 +13,25 @@ export const sanitizeHTML = (htmlString: string) => { const sanitizedText = DOMPurify.sanitize(htmlString, { ALLOWED_TAGS: [] }); // sanitize the string to remove all HTML tags return sanitizedText.trim(); // trim the string to remove leading and trailing whitespaces }; + +/** + * @description + * This function test whether a URL is valid or not. + * + * It accepts URLs with or without the protocol. + * @param {string} url + * @returns {boolean} + * @example + * checkURLValidity("https://example.com") => true + * checkURLValidity("example.com") => true + * checkURLValidity("example") => false + */ +export const checkURLValidity = (url: string): boolean => { + if (!url) return false; + + // regex to support complex query parameters and fragments + const urlPattern = + /^(https?:\/\/)?((([a-z\d-]+\.)*[a-z\d-]+\.[a-z]{2,6})|(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}))(:\d+)?(\/[\w.-]*)*(\?[^#\s]*)?(#[\w-]*)?$/i; + + return urlPattern.test(url); +}; diff --git a/packages/utils/src/theme.ts b/packages/utils/src/theme.ts new file mode 100644 index 00000000000..1f2c22b0249 --- /dev/null +++ b/packages/utils/src/theme.ts @@ -0,0 +1,2 @@ +export const resolveGeneralTheme = (resolvedTheme: string | undefined) => + resolvedTheme?.includes("light") ? "light" : resolvedTheme?.includes("dark") ? "dark" : "system";