From 500c414ecdf477501a9c694d12943b1d2b988410 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Fri, 19 Jan 2024 14:06:13 +0530 Subject: [PATCH 1/9] chore: sign up workflow updated --- packages/types/src/app.d.ts | 10 +- web/components/account/index.ts | 1 + .../account/sign-in-forms/o-auth-options.tsx | 6 +- .../account/sign-up-forms/email-form.tsx | 121 ++++++++++ web/components/account/sign-up-forms/index.ts | 5 + .../sign-up-forms/optional-set-password.tsx | 166 +++++++++++++ .../account/sign-up-forms/password.tsx | 150 ++++++++++++ web/components/account/sign-up-forms/root.tsx | 82 +++++++ .../account/sign-up-forms/unique-code.tsx | 220 ++++++++++++++++++ web/components/page-views/signin.tsx | 26 +-- web/pages/accounts/sign-up.tsx | 101 +++----- web/store/application/app-config.store.ts | 3 +- 12 files changed, 789 insertions(+), 102 deletions(-) create mode 100644 web/components/account/sign-up-forms/email-form.tsx create mode 100644 web/components/account/sign-up-forms/index.ts create mode 100644 web/components/account/sign-up-forms/optional-set-password.tsx create mode 100644 web/components/account/sign-up-forms/password.tsx create mode 100644 web/components/account/sign-up-forms/root.tsx create mode 100644 web/components/account/sign-up-forms/unique-code.tsx diff --git a/packages/types/src/app.d.ts b/packages/types/src/app.d.ts index 92b304e1799..06a433ddda5 100644 --- a/packages/types/src/app.d.ts +++ b/packages/types/src/app.d.ts @@ -1,14 +1,14 @@ export interface IAppConfig { email_password_login: boolean; file_size_limit: number; - google_client_id: string | null; github_app_name: string | null; github_client_id: string | null; - magic_login: boolean; - slack_client_id: string | null; - posthog_api_key: string | null; - posthog_host: string | null; + google_client_id: string | null; has_openai_configured: boolean; has_unsplash_configured: boolean; is_smtp_configured: boolean; + magic_login: boolean; + posthog_api_key: string | null; + posthog_host: string | null; + slack_client_id: string | null; } diff --git a/web/components/account/index.ts b/web/components/account/index.ts index 275f7ff087b..70b496c9afb 100644 --- a/web/components/account/index.ts +++ b/web/components/account/index.ts @@ -1,4 +1,5 @@ export * from "./sign-in-forms"; +export * from "./sign-up-forms"; export * from "./deactivate-account-modal"; export * from "./github-sign-in"; export * from "./google-sign-in"; diff --git a/web/components/account/sign-in-forms/o-auth-options.tsx b/web/components/account/sign-in-forms/o-auth-options.tsx index 9ed4e7e5ff1..610d1ac590b 100644 --- a/web/components/account/sign-in-forms/o-auth-options.tsx +++ b/web/components/account/sign-in-forms/o-auth-options.tsx @@ -72,9 +72,11 @@ export const OAuthOptions: React.FC = observer((props) => {

Or continue with


-
+
{envConfig?.google_client_id && ( - +
+ +
)} {envConfig?.github_client_id && ( diff --git a/web/components/account/sign-up-forms/email-form.tsx b/web/components/account/sign-up-forms/email-form.tsx new file mode 100644 index 00000000000..b57f672d0df --- /dev/null +++ b/web/components/account/sign-up-forms/email-form.tsx @@ -0,0 +1,121 @@ +import React from "react"; +import { Controller, useForm } from "react-hook-form"; +import { XCircle } from "lucide-react"; +import { observer } from "mobx-react-lite"; +// services +import { AuthService } from "services/auth.service"; +// hooks +import useToast from "hooks/use-toast"; +import { useApplication } from "hooks/store"; +// ui +import { Button, Input } from "@plane/ui"; +// helpers +import { checkEmailValidity } from "helpers/string.helper"; +// types +import { IEmailCheckData } from "@plane/types"; +// constants +import { ESignUpSteps } from "components/account"; + +type Props = { + handleStepChange: (step: ESignUpSteps) => void; + updateEmail: (email: string) => void; +}; + +type TEmailFormValues = { + email: string; +}; + +const authService = new AuthService(); + +export const SignUpEmailForm: React.FC = observer((props) => { + const { handleStepChange, updateEmail } = props; + // hooks + const { setToastAlert } = useToast(); + const { + config: { envConfig }, + } = useApplication(); + const { + control, + formState: { errors, isSubmitting, isValid }, + handleSubmit, + } = useForm({ + defaultValues: { + email: "", + }, + mode: "onChange", + reValidateMode: "onChange", + }); + + const handleFormSubmit = async (data: TEmailFormValues) => { + const payload: IEmailCheckData = { + email: data.email, + }; + + // update the global email state + updateEmail(data.email); + + await authService + .emailCheck(payload) + .then(() => { + // if SMTP is configured, send the user to unique code form + if (envConfig?.is_smtp_configured) handleStepChange(ESignUpSteps.UNIQUE_CODE); + // if SMTP is not configured, send the user to password form + else handleStepChange(ESignUpSteps.PASSWORD); + }) + .catch((err) => + setToastAlert({ + type: "error", + title: "Error!", + message: err?.error ?? "Something went wrong. Please try again.", + }) + ); + }; + + return ( + <> +

+ Get on your flight deck +

+

+ Create or join a workspace. Start with your e-mail. +

+ +
+
+ checkEmailValidity(value) || "Email is invalid", + }} + render={({ field: { value, onChange } }) => ( +
+ + {value.length > 0 && ( + onChange("")} + /> + )} +
+ )} + /> +
+ +
+ + ); +}); diff --git a/web/components/account/sign-up-forms/index.ts b/web/components/account/sign-up-forms/index.ts new file mode 100644 index 00000000000..256e85230c8 --- /dev/null +++ b/web/components/account/sign-up-forms/index.ts @@ -0,0 +1,5 @@ +export * from "./email-form"; +export * from "./optional-set-password"; +export * from "./password"; +export * from "./root"; +export * from "./unique-code"; diff --git a/web/components/account/sign-up-forms/optional-set-password.tsx b/web/components/account/sign-up-forms/optional-set-password.tsx new file mode 100644 index 00000000000..c3fde11f550 --- /dev/null +++ b/web/components/account/sign-up-forms/optional-set-password.tsx @@ -0,0 +1,166 @@ +import React, { useEffect, useState } from "react"; +import { Controller, useForm } from "react-hook-form"; +// services +import { AuthService } from "services/auth.service"; +// hooks +import useToast from "hooks/use-toast"; +// ui +import { Button, Input } from "@plane/ui"; +// helpers +import { checkEmailValidity } from "helpers/string.helper"; +// constants +import { ESignUpSteps } from "components/account"; + +type Props = { + email: string; + handleStepChange: (step: ESignUpSteps) => void; + handleSignInRedirection: () => Promise; +}; + +type TCreatePasswordFormValues = { + email: string; + password: string; +}; + +const defaultValues: TCreatePasswordFormValues = { + email: "", + password: "", +}; + +// services +const authService = new AuthService(); + +export const SignUpOptionalSetPasswordForm: React.FC = (props) => { + const { email, handleSignInRedirection } = props; + // states + const [isGoingToWorkspace, setIsGoingToWorkspace] = useState(false); + // toast alert + const { setToastAlert } = useToast(); + // form info + const { + control, + formState: { errors, isSubmitting, isValid }, + handleSubmit, + setFocus, + } = useForm({ + defaultValues: { + ...defaultValues, + email, + }, + mode: "onChange", + reValidateMode: "onChange", + }); + + const handleCreatePassword = async (formData: TCreatePasswordFormValues) => { + const payload = { + password: formData.password, + }; + + await authService + .setPassword(payload) + .then(async () => { + setToastAlert({ + type: "success", + title: "Success!", + message: "Password created successfully.", + }); + await handleSignInRedirection(); + }) + .catch((err) => + setToastAlert({ + type: "error", + title: "Error!", + message: err?.error ?? "Something went wrong. Please try again.", + }) + ); + }; + + const handleGoToWorkspace = async () => { + setIsGoingToWorkspace(true); + + await handleSignInRedirection().finally(() => setIsGoingToWorkspace(false)); + }; + + useEffect(() => { + setFocus("password"); + }, [setFocus]); + + return ( + <> +

Moving to the runway

+

+ Let{"'"}s set a password so you can do away with codes. +

+
+ checkEmailValidity(value) || "Email is invalid", + }} + render={({ field: { value, onChange, ref } }) => ( + + )} + /> +
+ ( + + )} + /> +

+ This password will continue to be your account{"'"}s password +

+
+
+ + +
+ + + ); +}; diff --git a/web/components/account/sign-up-forms/password.tsx b/web/components/account/sign-up-forms/password.tsx new file mode 100644 index 00000000000..7ae76878553 --- /dev/null +++ b/web/components/account/sign-up-forms/password.tsx @@ -0,0 +1,150 @@ +import React, { useEffect } from "react"; +import Link from "next/link"; +import { observer } from "mobx-react-lite"; +import { Controller, useForm } from "react-hook-form"; +import { XCircle } from "lucide-react"; +// services +import { AuthService } from "services/auth.service"; +// hooks +import useToast from "hooks/use-toast"; +// ui +import { Button, Input } from "@plane/ui"; +// helpers +import { checkEmailValidity } from "helpers/string.helper"; +// types +import { IPasswordSignInData } from "@plane/types"; + +type Props = { + email: string; + handleSignInRedirection: () => Promise; + handleEmailClear: () => void; +}; + +type TPasswordFormValues = { + email: string; + password: string; +}; + +const defaultValues: TPasswordFormValues = { + email: "", + password: "", +}; + +const authService = new AuthService(); + +export const SignUpPasswordForm: React.FC = observer((props) => { + const { email, handleSignInRedirection, handleEmailClear } = props; + // toast alert + const { setToastAlert } = useToast(); + // form info + const { + control, + formState: { dirtyFields, errors, isSubmitting, isValid }, + handleSubmit, + setFocus, + } = useForm({ + defaultValues: { + ...defaultValues, + email, + }, + mode: "onChange", + reValidateMode: "onChange", + }); + + const handleFormSubmit = async (formData: TPasswordFormValues) => { + const payload: IPasswordSignInData = { + email: formData.email, + password: formData.password, + }; + + await authService + .passwordSignIn(payload) + .then(async () => await handleSignInRedirection()) + .catch((err) => + setToastAlert({ + type: "error", + title: "Error!", + message: err?.error ?? "Something went wrong. Please try again.", + }) + ); + }; + + useEffect(() => { + setFocus("password"); + }, [setFocus]); + + return ( + <> +

+ Moving to the runway +

+

+ Let{"'"}s set a password so you can do away with codes. +

+
+
+ checkEmailValidity(value) || "Email is invalid", + }} + render={({ field: { value, onChange } }) => ( +
+ + {value.length > 0 && ( + + )} +
+ )} + /> +
+
+ ( + + )} + /> +

+ This password will continue to be your account{"'"}s password +

+
+ +

+ When you click the button above, you agree with our{" "} + + terms and conditions of service. + +

+
+ + ); +}); diff --git a/web/components/account/sign-up-forms/root.tsx b/web/components/account/sign-up-forms/root.tsx new file mode 100644 index 00000000000..5777c1ab37f --- /dev/null +++ b/web/components/account/sign-up-forms/root.tsx @@ -0,0 +1,82 @@ +import React, { useState } from "react"; +import { observer } from "mobx-react-lite"; +// hooks +import { useApplication } from "hooks/store"; +import useSignInRedirection from "hooks/use-sign-in-redirection"; +// components +import { + OAuthOptions, + SignUpEmailForm, + SignUpOptionalSetPasswordForm, + SignUpPasswordForm, + SignUpUniqueCodeForm, +} from "components/account"; +import Link from "next/link"; + +export enum ESignUpSteps { + EMAIL = "EMAIL", + UNIQUE_CODE = "UNIQUE_CODE", + PASSWORD = "PASSWORD", + OPTIONAL_SET_PASSWORD = "OPTIONAL_SET_PASSWORD", +} + +export const SignUpRoot = observer(() => { + // states + const [signInStep, setSignInStep] = useState(ESignUpSteps.PASSWORD); + const [email, setEmail] = useState(""); + // sign in redirection hook + const { handleRedirection } = useSignInRedirection(); + // mobx store + const { + config: { envConfig }, + } = useApplication(); + + const isOAuthEnabled = envConfig && (envConfig.google_client_id || envConfig.github_client_id); + + return ( + <> +
+ <> + {signInStep === ESignUpSteps.EMAIL && ( + setSignInStep(step)} + updateEmail={(newEmail) => setEmail(newEmail)} + /> + )} + {signInStep === ESignUpSteps.UNIQUE_CODE && ( + setSignInStep(step)} + /> + )} + {signInStep === ESignUpSteps.PASSWORD && ( + setSignInStep(ESignUpSteps.EMAIL)} + handleSignInRedirection={handleRedirection} + /> + )} + {signInStep === ESignUpSteps.OPTIONAL_SET_PASSWORD && ( + setSignInStep(step)} + /> + )} + +
+ {isOAuthEnabled && signInStep === ESignUpSteps.EMAIL && ( + <> + +

+ Already using Plane?{" "} + + Sign in + +

+ + )} + + ); +}); diff --git a/web/components/account/sign-up-forms/unique-code.tsx b/web/components/account/sign-up-forms/unique-code.tsx new file mode 100644 index 00000000000..c8d571aeedc --- /dev/null +++ b/web/components/account/sign-up-forms/unique-code.tsx @@ -0,0 +1,220 @@ +import React, { useEffect, useState } from "react"; +import Link from "next/link"; +import { Controller, useForm } from "react-hook-form"; +import { XCircle } from "lucide-react"; +// services +import { AuthService } from "services/auth.service"; +import { UserService } from "services/user.service"; +// hooks +import useToast from "hooks/use-toast"; +import useTimer from "hooks/use-timer"; +// ui +import { Button, Input } from "@plane/ui"; +// helpers +import { checkEmailValidity } from "helpers/string.helper"; +// types +import { IEmailCheckData, IMagicSignInData } from "@plane/types"; +// constants +import { ESignUpSteps } from "components/account"; + +type Props = { + email: string; + handleStepChange: (step: ESignUpSteps) => void; + handleSignInRedirection: () => Promise; +}; + +type TUniqueCodeFormValues = { + email: string; + token: string; +}; + +const defaultValues: TUniqueCodeFormValues = { + email: "", + token: "", +}; + +// services +const authService = new AuthService(); +const userService = new UserService(); + +export const SignUpUniqueCodeForm: React.FC = (props) => { + const { email, handleStepChange, handleSignInRedirection } = props; + // states + const [isRequestingNewCode, setIsRequestingNewCode] = useState(false); + // toast alert + const { setToastAlert } = useToast(); + // timer + const { timer: resendTimerCode, setTimer: setResendCodeTimer } = useTimer(30); + // form info + const { + control, + formState: { errors, isSubmitting, isValid }, + getValues, + handleSubmit, + reset, + setFocus, + } = useForm({ + defaultValues: { + ...defaultValues, + email, + }, + mode: "onChange", + reValidateMode: "onChange", + }); + + const handleUniqueCodeSignIn = async (formData: TUniqueCodeFormValues) => { + const payload: IMagicSignInData = { + email: formData.email, + key: `magic_${formData.email}`, + token: formData.token, + }; + + await authService + .magicSignIn(payload) + .then(async () => { + const currentUser = await userService.currentUser(); + + if (currentUser.is_password_autoset) handleStepChange(ESignUpSteps.OPTIONAL_SET_PASSWORD); + else await handleSignInRedirection(); + }) + .catch((err) => + setToastAlert({ + type: "error", + title: "Error!", + message: err?.error ?? "Something went wrong. Please try again.", + }) + ); + }; + + const handleSendNewCode = async (formData: TUniqueCodeFormValues) => { + const payload: IEmailCheckData = { + email: formData.email, + }; + + await authService + .generateUniqueCode(payload) + .then(() => { + setResendCodeTimer(30); + setToastAlert({ + type: "success", + title: "Success!", + message: "A new unique code has been sent to your email.", + }); + + reset({ + email: formData.email, + token: "", + }); + }) + .catch((err) => + setToastAlert({ + type: "error", + title: "Error!", + message: err?.error ?? "Something went wrong. Please try again.", + }) + ); + }; + + const handleRequestNewCode = async () => { + setIsRequestingNewCode(true); + + await handleSendNewCode(getValues()) + .then(() => setResendCodeTimer(30)) + .finally(() => setIsRequestingNewCode(false)); + }; + + const isRequestNewCodeDisabled = isRequestingNewCode || resendTimerCode > 0; + + useEffect(() => { + setFocus("token"); + }, [setFocus]); + + return ( + <> +

Moving to the runway

+

+ Paste the code you got at {email} below. +

+ +
+
+ checkEmailValidity(value) || "Email is invalid", + }} + render={({ field: { value, onChange, ref } }) => ( +
+ + {value.length > 0 && ( + handleStepChange(ESignUpSteps.EMAIL)} + /> + )} +
+ )} + /> +
+
+ ( + + )} + /> +
+ +
+
+ +

+ When you click the button above, you agree with our{" "} + + terms and conditions of service. + +

+
+ + ); +}; diff --git a/web/components/page-views/signin.tsx b/web/components/page-views/signin.tsx index e4810ec2da3..89bca5d6275 100644 --- a/web/components/page-views/signin.tsx +++ b/web/components/page-views/signin.tsx @@ -7,7 +7,7 @@ import useSignInRedirection from "hooks/use-sign-in-redirection"; // components import { SignInRoot } from "components/account"; // ui -import { Loader, Spinner } from "@plane/ui"; +import { Spinner } from "@plane/ui"; // images import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png"; @@ -26,7 +26,7 @@ export const SignInView = observer(() => { handleRedirection(); }, [handleRedirection]); - if (isRedirecting || currentUser) + if (isRedirecting || currentUser || !envConfig) return (
@@ -35,32 +35,16 @@ export const SignInView = observer(() => { return (
-
+
Plane Logo Plane
-
+
- {!envConfig ? ( -
-
- - - - - - - - - -
-
- ) : ( - - )} +
diff --git a/web/pages/accounts/sign-up.tsx b/web/pages/accounts/sign-up.tsx index 390d91ec9dd..5b5648439c5 100644 --- a/web/pages/accounts/sign-up.tsx +++ b/web/pages/accounts/sign-up.tsx @@ -1,97 +1,52 @@ -import React, { useEffect, ReactElement } from "react"; +import React from "react"; import Image from "next/image"; -import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; -// next-themes -import { useTheme } from "next-themes"; -// services -import { AuthService } from "services/auth.service"; // hooks -import { useUser } from "hooks/store"; -import useUserAuth from "hooks/use-user-auth"; -import useToast from "hooks/use-toast"; +import { useApplication, useUser } from "hooks/store"; // layouts import DefaultLayout from "layouts/default-layout"; // components -import { EmailSignUpForm } from "components/account"; -// images +import { SignUpRoot } from "components/account"; +// ui +import { Spinner } from "@plane/ui"; +// assets import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png"; // types import { NextPageWithLayout } from "lib/types"; -type EmailPasswordFormValues = { - email: string; - password?: string; - medium?: string; -}; - -// services -const authService = new AuthService(); - const SignUpPage: NextPageWithLayout = observer(() => { - // router - const router = useRouter(); - // toast alert - const { setToastAlert } = useToast(); - // next-themes - const { setTheme } = useTheme(); // store hooks - const { currentUser, fetchCurrentUser, currentUserLoader } = useUser(); - // custom hooks - const {} = useUserAuth({ routeAuth: "sign-in", user: currentUser, isLoading: currentUserLoader }); - - const handleSignUp = async (formData: EmailPasswordFormValues) => { - const payload = { - email: formData.email, - password: formData.password ?? "", - }; - - await authService - .emailSignUp(payload) - .then(async (response) => { - setToastAlert({ - type: "success", - title: "Success!", - message: "Account created successfully.", - }); - - if (response) await fetchCurrentUser(); - router.push("/onboarding"); - }) - .catch((err) => - setToastAlert({ - type: "error", - title: "Error!", - message: err?.error || "Something went wrong. Please try again later or contact the support team.", - }) - ); - }; - - useEffect(() => { - setTheme("system"); - }, [setTheme]); + const { + config: { envConfig }, + } = useApplication(); + const { currentUser } = useUser(); + + if (currentUser || !envConfig) + return ( +
+ +
+ ); return ( - <> -
-
-
-
- Plane Logo -
+
+
+
+ Plane Logo + Plane
-
-
-

SignUp on Plane

- + +
+
+
- +
); }); -SignUpPage.getLayout = function getLayout(page: ReactElement) { +SignUpPage.getLayout = function getLayout(page: React.ReactElement) { return {page}; }; diff --git a/web/store/application/app-config.store.ts b/web/store/application/app-config.store.ts index f95177f7804..6faef8b697f 100644 --- a/web/store/application/app-config.store.ts +++ b/web/store/application/app-config.store.ts @@ -5,8 +5,9 @@ import { IAppConfig } from "@plane/types"; import { AppConfigService } from "services/app_config.service"; export interface IAppConfigStore { + // observables envConfig: IAppConfig | null; - // action + // actions fetchAppConfig: () => Promise; } From 4d81c7a52837f310334edf7c1126a82fcbe20e3a Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Fri, 19 Jan 2024 14:42:20 +0530 Subject: [PATCH 2/9] chore: sign in workflow updated --- .../account/sign-in-forms/create-password.tsx | 141 ----------------- .../account/sign-in-forms/email-form.tsx | 21 +-- web/components/account/sign-in-forms/index.ts | 3 - .../sign-in-forms/optional-set-password.tsx | 116 ++++++++++---- .../account/sign-in-forms/password.tsx | 84 +++------- web/components/account/sign-in-forms/root.tsx | 68 ++++----- .../sign-in-forms/self-hosted-sign-in.tsx | 144 ------------------ .../sign-in-forms/set-password-link.tsx | 103 ------------- .../account/sign-in-forms/unique-code.tsx | 73 ++------- .../sign-up-forms/optional-set-password.tsx | 2 +- .../account/sign-up-forms/password.tsx | 2 +- 11 files changed, 160 insertions(+), 597 deletions(-) delete mode 100644 web/components/account/sign-in-forms/create-password.tsx delete mode 100644 web/components/account/sign-in-forms/self-hosted-sign-in.tsx delete mode 100644 web/components/account/sign-in-forms/set-password-link.tsx diff --git a/web/components/account/sign-in-forms/create-password.tsx b/web/components/account/sign-in-forms/create-password.tsx deleted file mode 100644 index cf53078bedf..00000000000 --- a/web/components/account/sign-in-forms/create-password.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import React, { useEffect } from "react"; -import Link from "next/link"; -import { Controller, useForm } from "react-hook-form"; -// services -import { AuthService } from "services/auth.service"; -// hooks -import useToast from "hooks/use-toast"; -// ui -import { Button, Input } from "@plane/ui"; -// helpers -import { checkEmailValidity } from "helpers/string.helper"; -// constants -import { ESignInSteps } from "components/account"; - -type Props = { - email: string; - handleStepChange: (step: ESignInSteps) => void; - handleSignInRedirection: () => Promise; - isOnboarded: boolean; -}; - -type TCreatePasswordFormValues = { - email: string; - password: string; -}; - -const defaultValues: TCreatePasswordFormValues = { - email: "", - password: "", -}; - -// services -const authService = new AuthService(); - -export const CreatePasswordForm: React.FC = (props) => { - const { email, handleSignInRedirection, isOnboarded } = props; - // toast alert - const { setToastAlert } = useToast(); - // form info - const { - control, - formState: { errors, isSubmitting, isValid }, - handleSubmit, - setFocus, - } = useForm({ - defaultValues: { - ...defaultValues, - email, - }, - mode: "onChange", - reValidateMode: "onChange", - }); - - const handleCreatePassword = async (formData: TCreatePasswordFormValues) => { - const payload = { - password: formData.password, - }; - - await authService - .setPassword(payload) - .then(async () => { - setToastAlert({ - type: "success", - title: "Success!", - message: "Password created successfully.", - }); - await handleSignInRedirection(); - }) - .catch((err) => - setToastAlert({ - type: "error", - title: "Error!", - message: err?.error ?? "Something went wrong. Please try again.", - }) - ); - }; - - useEffect(() => { - setFocus("password"); - }, [setFocus]); - - return ( - <> -

- Get on your flight deck -

-
- checkEmailValidity(value) || "Email is invalid", - }} - render={({ field: { value, onChange, ref } }) => ( - - )} - /> - ( - - )} - /> - -

- When you click the button above, you agree with our{" "} - - terms and conditions of service. - -

- - - ); -}; diff --git a/web/components/account/sign-in-forms/email-form.tsx b/web/components/account/sign-in-forms/email-form.tsx index 7642c3b9984..ed28572b45d 100644 --- a/web/components/account/sign-in-forms/email-form.tsx +++ b/web/components/account/sign-in-forms/email-form.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from "react"; +import React from "react"; import { Controller, useForm } from "react-hook-form"; import { XCircle } from "lucide-react"; import { observer } from "mobx-react-lite"; @@ -27,7 +27,7 @@ type TEmailFormValues = { const authService = new AuthService(); -export const EmailForm: React.FC = observer((props) => { +export const SignInEmailForm: React.FC = observer((props) => { const { handleStepChange, updateEmail } = props; // hooks const { setToastAlert } = useToast(); @@ -38,7 +38,6 @@ export const EmailForm: React.FC = observer((props) => { control, formState: { errors, isSubmitting, isValid }, handleSubmit, - setFocus, } = useForm({ defaultValues: { email: "", @@ -59,9 +58,7 @@ export const EmailForm: React.FC = observer((props) => { .emailCheck(payload) .then((res) => { // if the password has been auto set, send the user to magic sign-in - if (res.is_password_autoset && envConfig?.is_smtp_configured) { - handleStepChange(ESignInSteps.UNIQUE_CODE); - } + if (envConfig?.is_smtp_configured && res.is_password_autoset) handleStepChange(ESignInSteps.UNIQUE_CODE); // if the password has not been auto set, send them to password sign-in else handleStepChange(ESignInSteps.PASSWORD); }) @@ -74,17 +71,13 @@ export const EmailForm: React.FC = observer((props) => { ); }; - useEffect(() => { - setFocus("email"); - }, [setFocus]); - return ( <>

- Get on your flight deck + Welcome back, let{"'"}s get you on board

- Create or join a workspace. Start with your e-mail. + Get back to your issues, projects and workspaces

@@ -96,7 +89,7 @@ export const EmailForm: React.FC = observer((props) => { required: "Email is required", validate: (value) => checkEmailValidity(value) || "Email is invalid", }} - render={({ field: { value, onChange, ref } }) => ( + render={({ field: { value, onChange } }) => (
= observer((props) => { type="email" value={value} onChange={onChange} - ref={ref} hasError={Boolean(errors.email)} placeholder="orville.wright@frstflt.com" className="h-[46px] w-full border border-onboarding-border-100 pr-12 placeholder:text-onboarding-text-400" + autoFocus /> {value.length > 0 && ( void; handleSignInRedirection: () => Promise; - isOnboarded: boolean; }; -export const OptionalSetPasswordForm: React.FC = (props) => { - const { email, handleStepChange, handleSignInRedirection, isOnboarded } = props; +type TCreatePasswordFormValues = { + email: string; + password: string; +}; + +const defaultValues: TCreatePasswordFormValues = { + email: "", + password: "", +}; + +// services +const authService = new AuthService(); + +export const SignInOptionalSetPasswordForm: React.FC = (props) => { + const { email, handleSignInRedirection } = props; // states const [isGoingToWorkspace, setIsGoingToWorkspace] = useState(false); + // toast alert + const { setToastAlert } = useToast(); // form info const { control, - formState: { errors, isValid }, - } = useForm({ + formState: { errors, isSubmitting, isValid }, + handleSubmit, + setFocus, + } = useForm({ defaultValues: { + ...defaultValues, email, }, mode: "onChange", reValidateMode: "onChange", }); + const handleCreatePassword = async (formData: TCreatePasswordFormValues) => { + const payload = { + password: formData.password, + }; + + await authService + .setPassword(payload) + .then(async () => { + setToastAlert({ + type: "success", + title: "Success!", + message: "Password created successfully.", + }); + await handleSignInRedirection(); + }) + .catch((err) => + setToastAlert({ + type: "error", + title: "Error!", + message: err?.error ?? "Something went wrong. Please try again.", + }) + ); + }; + const handleGoToWorkspace = async () => { setIsGoingToWorkspace(true); await handleSignInRedirection().finally(() => setIsGoingToWorkspace(false)); }; + useEffect(() => { + setFocus("password"); + }, [setFocus]); + return ( <> -

Set a password

-

- If you{"'"}d like to do away with codes, set a password here. +

Set your password

+

+ If you{"'"}d like to do away with codes, set a password here

- - + = (props) => { ref={ref} hasError={Boolean(errors.email)} placeholder="orville.wright@frstflt.com" - className="h-[46px] w-full border border-onboarding-border-100 pr-12 text-onboarding-text-400" + className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 text-onboarding-text-400" disabled /> )} /> -
+
+ ( + + )} + /> +

+ Whatever you choose now will be your account{"'"}s password until you change it. +

+
+
-

- When you click{" "} - {isOnboarded ? "Go to workspace" : "Set up workspace"} above, - you agree with our{" "} - - terms and conditions of service. - -

); diff --git a/web/components/account/sign-in-forms/password.tsx b/web/components/account/sign-in-forms/password.tsx index c2eb358f2dd..f6629ffa1bc 100644 --- a/web/components/account/sign-in-forms/password.tsx +++ b/web/components/account/sign-in-forms/password.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useState } from "react"; import Link from "next/link"; +import { observer } from "mobx-react-lite"; import { Controller, useForm } from "react-hook-form"; import { XCircle } from "lucide-react"; // services @@ -15,7 +16,6 @@ import { checkEmailValidity } from "helpers/string.helper"; import { IPasswordSignInData } from "@plane/types"; // constants import { ESignInSteps } from "components/account"; -import { observer } from "mobx-react-lite"; type Props = { email: string; @@ -37,11 +37,10 @@ const defaultValues: TPasswordFormValues = { const authService = new AuthService(); -export const PasswordForm: React.FC = observer((props) => { +export const SignInPasswordForm: React.FC = observer((props) => { const { email, updateEmail, handleStepChange, handleSignInRedirection, handleEmailClear } = props; // states const [isSendingUniqueCode, setIsSendingUniqueCode] = useState(false); - const [isSendingResetPasswordLink, setIsSendingResetPasswordLink] = useState(false); // toast alert const { setToastAlert } = useToast(); const { @@ -84,31 +83,6 @@ export const PasswordForm: React.FC = observer((props) => { ); }; - const handleForgotPassword = async () => { - const emailFormValue = getValues("email"); - - const isEmailValid = checkEmailValidity(emailFormValue); - - if (!isEmailValid) { - setError("email", { message: "Email is invalid" }); - return; - } - - setIsSendingResetPasswordLink(true); - - authService - .sendResetPasswordLink({ email: emailFormValue }) - .then(() => handleStepChange(ESignInSteps.SET_PASSWORD_LINK)) - .catch((err) => - setToastAlert({ - type: "error", - title: "Error!", - message: err?.error ?? "Something went wrong. Please try again.", - }) - ) - .finally(() => setIsSendingResetPasswordLink(false)); - }; - const handleSendUniqueCode = async () => { const emailFormValue = getValues("email"); @@ -140,10 +114,13 @@ export const PasswordForm: React.FC = observer((props) => { return ( <> -

- Get on your flight deck +

+ Welcome back, let{"'"}s get you on board

-
+

+ Get back to your issues, projects and workspaces. +

+
= observer((props) => { /> )} /> -
- +
+ + Forgot your password? +
-
+
+ {envConfig && envConfig.is_smtp_configured && ( )} -
-

- When you click Go to workspace above, you agree with our{" "} - - terms and conditions of service. - -

); diff --git a/web/components/account/sign-in-forms/root.tsx b/web/components/account/sign-in-forms/root.tsx index 80f46c63ede..dd56396503e 100644 --- a/web/components/account/sign-in-forms/root.tsx +++ b/web/components/account/sign-in-forms/root.tsx @@ -1,4 +1,5 @@ import React, { useState } from "react"; +import Link from "next/link"; import { observer } from "mobx-react-lite"; // hooks import { useApplication } from "hooks/store"; @@ -6,32 +7,25 @@ import useSignInRedirection from "hooks/use-sign-in-redirection"; // components import { LatestFeatureBlock } from "components/common"; import { - EmailForm, - UniqueCodeForm, - PasswordForm, - SetPasswordLink, + SignInEmailForm, + SignInUniqueCodeForm, + SignInPasswordForm, OAuthOptions, - OptionalSetPasswordForm, - CreatePasswordForm, + SignInOptionalSetPasswordForm, } from "components/account"; export enum ESignInSteps { EMAIL = "EMAIL", PASSWORD = "PASSWORD", - SET_PASSWORD_LINK = "SET_PASSWORD_LINK", UNIQUE_CODE = "UNIQUE_CODE", OPTIONAL_SET_PASSWORD = "OPTIONAL_SET_PASSWORD", - CREATE_PASSWORD = "CREATE_PASSWORD", USE_UNIQUE_CODE_FROM_PASSWORD = "USE_UNIQUE_CODE_FROM_PASSWORD", } -const OAUTH_HIDDEN_STEPS = [ESignInSteps.OPTIONAL_SET_PASSWORD, ESignInSteps.CREATE_PASSWORD]; - export const SignInRoot = observer(() => { // states const [signInStep, setSignInStep] = useState(ESignInSteps.EMAIL); const [email, setEmail] = useState(""); - const [isOnboarded, setIsOnboarded] = useState(false); // sign in redirection hook const { handleRedirection } = useSignInRedirection(); // mobx store @@ -46,74 +40,66 @@ export const SignInRoot = observer(() => {
<> {signInStep === ESignInSteps.EMAIL && ( - setSignInStep(step)} updateEmail={(newEmail) => setEmail(newEmail)} /> )} - {signInStep === ESignInSteps.PASSWORD && ( - setEmail(newEmail)} handleStepChange={(step) => setSignInStep(step)} + handleSignInRedirection={handleRedirection} handleEmailClear={() => { setEmail(""); setSignInStep(ESignInSteps.EMAIL); }} - handleSignInRedirection={handleRedirection} + submitButtonText="Continue" /> )} - {signInStep === ESignInSteps.SET_PASSWORD_LINK && ( - setEmail(newEmail)} /> - )} - {signInStep === ESignInSteps.USE_UNIQUE_CODE_FROM_PASSWORD && ( - setEmail(newEmail)} handleStepChange={(step) => setSignInStep(step)} - handleSignInRedirection={handleRedirection} - submitButtonLabel="Go to workspace" - showTermsAndConditions - updateUserOnboardingStatus={(value) => setIsOnboarded(value)} handleEmailClear={() => { setEmail(""); setSignInStep(ESignInSteps.EMAIL); }} + handleSignInRedirection={handleRedirection} /> )} - {signInStep === ESignInSteps.UNIQUE_CODE && ( - setEmail(newEmail)} handleStepChange={(step) => setSignInStep(step)} handleSignInRedirection={handleRedirection} - updateUserOnboardingStatus={(value) => setIsOnboarded(value)} handleEmailClear={() => { setEmail(""); setSignInStep(ESignInSteps.EMAIL); }} + submitButtonText="Go to workspace" /> )} {signInStep === ESignInSteps.OPTIONAL_SET_PASSWORD && ( - setSignInStep(step)} - handleSignInRedirection={handleRedirection} - isOnboarded={isOnboarded} - /> - )} - {signInStep === ESignInSteps.CREATE_PASSWORD && ( - setSignInStep(step)} handleSignInRedirection={handleRedirection} - isOnboarded={isOnboarded} /> )}
- {isOAuthEnabled && !OAUTH_HIDDEN_STEPS.includes(signInStep) && ( - + {isOAuthEnabled && signInStep === ESignInSteps.EMAIL && ( + <> + +

+ Don{"'"}t have an account?{" "} + + Sign up + +

+ )} diff --git a/web/components/account/sign-in-forms/self-hosted-sign-in.tsx b/web/components/account/sign-in-forms/self-hosted-sign-in.tsx deleted file mode 100644 index bcecef20aad..00000000000 --- a/web/components/account/sign-in-forms/self-hosted-sign-in.tsx +++ /dev/null @@ -1,144 +0,0 @@ -import React, { useEffect } from "react"; -import Link from "next/link"; -import { Controller, useForm } from "react-hook-form"; -import { XCircle } from "lucide-react"; -// services -import { AuthService } from "services/auth.service"; -// hooks -import useToast from "hooks/use-toast"; -// ui -import { Button, Input } from "@plane/ui"; -// helpers -import { checkEmailValidity } from "helpers/string.helper"; -// types -import { IPasswordSignInData } from "@plane/types"; - -type Props = { - email: string; - updateEmail: (email: string) => void; - handleSignInRedirection: () => Promise; -}; - -type TPasswordFormValues = { - email: string; - password: string; -}; - -const defaultValues: TPasswordFormValues = { - email: "", - password: "", -}; - -const authService = new AuthService(); - -export const SelfHostedSignInForm: React.FC = (props) => { - const { email, updateEmail, handleSignInRedirection } = props; - // toast alert - const { setToastAlert } = useToast(); - // form info - const { - control, - formState: { dirtyFields, errors, isSubmitting }, - handleSubmit, - setFocus, - } = useForm({ - defaultValues: { - ...defaultValues, - email, - }, - mode: "onChange", - reValidateMode: "onChange", - }); - - const handleFormSubmit = async (formData: TPasswordFormValues) => { - const payload: IPasswordSignInData = { - email: formData.email, - password: formData.password, - }; - - updateEmail(formData.email); - - await authService - .passwordSignIn(payload) - .then(async () => await handleSignInRedirection()) - .catch((err) => - setToastAlert({ - type: "error", - title: "Error!", - message: err?.error ?? "Something went wrong. Please try again.", - }) - ); - }; - - useEffect(() => { - setFocus("email"); - }, [setFocus]); - - return ( - <> -

- Get on your flight deck -

-
-
- checkEmailValidity(value) || "Email is invalid", - }} - render={({ field: { value, onChange } }) => ( -
- - {value.length > 0 && ( - onChange("")} - /> - )} -
- )} - /> -
-
- ( - - )} - /> -
- -

- When you click the button above, you agree with our{" "} - - terms and conditions of service. - -

-
- - ); -}; diff --git a/web/components/account/sign-in-forms/set-password-link.tsx b/web/components/account/sign-in-forms/set-password-link.tsx deleted file mode 100644 index 788142d8037..00000000000 --- a/web/components/account/sign-in-forms/set-password-link.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import React from "react"; -import { Controller, useForm } from "react-hook-form"; -// services -import { AuthService } from "services/auth.service"; -// hooks -import useToast from "hooks/use-toast"; -// ui -import { Button, Input } from "@plane/ui"; -// helpers -import { checkEmailValidity } from "helpers/string.helper"; -// types -import { IEmailCheckData } from "@plane/types"; - -type Props = { - email: string; - updateEmail: (email: string) => void; -}; - -const authService = new AuthService(); - -export const SetPasswordLink: React.FC = (props) => { - const { email, updateEmail } = props; - - const { setToastAlert } = useToast(); - - const { - control, - formState: { errors, isSubmitting, isValid }, - handleSubmit, - } = useForm({ - defaultValues: { - email, - }, - mode: "onChange", - reValidateMode: "onChange", - }); - - const handleSendNewLink = async (formData: { email: string }) => { - updateEmail(formData.email); - - const payload: IEmailCheckData = { - email: formData.email, - }; - - await authService - .sendResetPasswordLink(payload) - .then(() => - setToastAlert({ - type: "success", - title: "Success!", - message: "We have sent a new link to your email.", - }) - ) - .catch((err) => - setToastAlert({ - type: "error", - title: "Error!", - message: err?.error ?? "Something went wrong. Please try again.", - }) - ); - }; - - return ( - <> -

- Get on your flight deck -

-

- We have sent a link to {email}, so you can set a - password -

- -
-
- checkEmailValidity(value) || "Email is invalid", - }} - render={({ field: { value, onChange } }) => ( - - )} - /> -
- -
- - ); -}; diff --git a/web/components/account/sign-in-forms/unique-code.tsx b/web/components/account/sign-in-forms/unique-code.tsx index 3ab75831d0f..18540efd6c5 100644 --- a/web/components/account/sign-in-forms/unique-code.tsx +++ b/web/components/account/sign-in-forms/unique-code.tsx @@ -1,7 +1,6 @@ import React, { useEffect, useState } from "react"; -import Link from "next/link"; import { Controller, useForm } from "react-hook-form"; -import { CornerDownLeft, XCircle } from "lucide-react"; +import { XCircle } from "lucide-react"; // services import { AuthService } from "services/auth.service"; import { UserService } from "services/user.service"; @@ -19,13 +18,10 @@ import { ESignInSteps } from "components/account"; type Props = { email: string; - updateEmail: (email: string) => void; handleStepChange: (step: ESignInSteps) => void; handleSignInRedirection: () => Promise; - submitButtonLabel?: string; - showTermsAndConditions?: boolean; - updateUserOnboardingStatus: (value: boolean) => void; handleEmailClear: () => void; + submitButtonText: string; }; type TUniqueCodeFormValues = { @@ -42,17 +38,8 @@ const defaultValues: TUniqueCodeFormValues = { const authService = new AuthService(); const userService = new UserService(); -export const UniqueCodeForm: React.FC = (props) => { - const { - email, - updateEmail, - handleStepChange, - handleSignInRedirection, - submitButtonLabel = "Continue", - showTermsAndConditions = false, - updateUserOnboardingStatus, - handleEmailClear, - } = props; +export const SignInUniqueCodeForm: React.FC = (props) => { + const { email, handleStepChange, handleSignInRedirection, handleEmailClear, submitButtonText } = props; // states const [isRequestingNewCode, setIsRequestingNewCode] = useState(false); // toast alert @@ -62,7 +49,7 @@ export const UniqueCodeForm: React.FC = (props) => { // form info const { control, - formState: { dirtyFields, errors, isSubmitting, isValid }, + formState: { errors, isSubmitting, isValid }, getValues, handleSubmit, reset, @@ -88,8 +75,6 @@ export const UniqueCodeForm: React.FC = (props) => { .then(async () => { const currentUser = await userService.currentUser(); - updateUserOnboardingStatus(currentUser.is_onboarded); - if (currentUser.is_password_autoset) handleStepChange(ESignInSteps.OPTIONAL_SET_PASSWORD); else await handleSignInRedirection(); }) @@ -131,13 +116,6 @@ export const UniqueCodeForm: React.FC = (props) => { ); }; - const handleFormSubmit = async (formData: TUniqueCodeFormValues) => { - updateEmail(formData.email); - - if (dirtyFields.email) await handleSendNewCode(formData); - else await handleUniqueCodeSignIn(formData); - }; - const handleRequestNewCode = async () => { setIsRequestingNewCode(true); @@ -147,21 +125,18 @@ export const UniqueCodeForm: React.FC = (props) => { }; const isRequestNewCodeDisabled = isRequestingNewCode || resendTimerCode > 0; - const hasEmailChanged = dirtyFields.email; useEffect(() => { setFocus("token"); }, [setFocus]); + return ( <> -

- Get on your flight deck -

+

Moving to the runway

Paste the code you got at {email} below.

- -
+
= (props) => { type="email" value={value} onChange={onChange} - onBlur={() => { - if (hasEmailChanged) handleSendNewCode(getValues()); - }} ref={ref} hasError={Boolean(errors.email)} placeholder="orville.wright@frstflt.com" @@ -196,21 +168,13 @@ export const UniqueCodeForm: React.FC = (props) => {
)} /> - {hasEmailChanged && ( - - )}
( = (props) => {
- - {showTermsAndConditions && ( -

- When you click the button above, you agree with our{" "} - - terms and conditions of service. - -

- )} ); diff --git a/web/components/account/sign-up-forms/optional-set-password.tsx b/web/components/account/sign-up-forms/optional-set-password.tsx index c3fde11f550..ecd4f82a864 100644 --- a/web/components/account/sign-up-forms/optional-set-password.tsx +++ b/web/components/account/sign-up-forms/optional-set-password.tsx @@ -134,7 +134,7 @@ export const SignUpOptionalSetPasswordForm: React.FC = (props) => { /> )} /> -

+

This password will continue to be your account{"'"}s password

diff --git a/web/components/account/sign-up-forms/password.tsx b/web/components/account/sign-up-forms/password.tsx index 7ae76878553..38151411678 100644 --- a/web/components/account/sign-up-forms/password.tsx +++ b/web/components/account/sign-up-forms/password.tsx @@ -131,7 +131,7 @@ export const SignUpPasswordForm: React.FC = observer((props) => { /> )} /> -

+

This password will continue to be your account{"'"}s password

From fdb1458414b1979bc08a9c57c29f75f0a58caba0 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Fri, 19 Jan 2024 14:49:08 +0530 Subject: [PATCH 3/9] refactor: folder structure --- web/components/account/email-signup-form.tsx | 137 ------------------ web/components/account/index.ts | 4 +- .../account/{ => o-auth}/github-sign-in.tsx | 0 .../account/{ => o-auth}/google-sign-in.tsx | 0 web/components/account/o-auth/index.ts | 3 + .../o-auth-options.tsx | 0 .../{email-form.tsx => email.tsx} | 0 web/components/account/sign-in-forms/index.ts | 3 +- .../{email-form.tsx => email.tsx} | 0 web/components/account/sign-up-forms/index.ts | 2 +- web/components/account/sign-up-forms/root.tsx | 2 +- 11 files changed, 7 insertions(+), 144 deletions(-) delete mode 100644 web/components/account/email-signup-form.tsx rename web/components/account/{ => o-auth}/github-sign-in.tsx (100%) rename web/components/account/{ => o-auth}/google-sign-in.tsx (100%) create mode 100644 web/components/account/o-auth/index.ts rename web/components/account/{sign-in-forms => o-auth}/o-auth-options.tsx (100%) rename web/components/account/sign-in-forms/{email-form.tsx => email.tsx} (100%) rename web/components/account/sign-up-forms/{email-form.tsx => email.tsx} (100%) diff --git a/web/components/account/email-signup-form.tsx b/web/components/account/email-signup-form.tsx deleted file mode 100644 index 8bbf859a408..00000000000 --- a/web/components/account/email-signup-form.tsx +++ /dev/null @@ -1,137 +0,0 @@ -import React from "react"; -import Link from "next/link"; -import { Controller, useForm } from "react-hook-form"; -// ui -import { Button, Input } from "@plane/ui"; -// types -type EmailPasswordFormValues = { - email: string; - password?: string; - confirm_password: string; - medium?: string; -}; - -type Props = { - onSubmit: (formData: EmailPasswordFormValues) => Promise; -}; - -export const EmailSignUpForm: React.FC = (props) => { - const { onSubmit } = props; - - const { - handleSubmit, - control, - watch, - formState: { errors, isSubmitting, isValid, isDirty }, - } = useForm({ - defaultValues: { - email: "", - password: "", - confirm_password: "", - medium: "email", - }, - mode: "onChange", - reValidateMode: "onChange", - }); - - return ( - <> -
-
- - /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test( - value - ) || "Email address is not valid", - }} - render={({ field: { value, onChange, ref } }) => ( - - )} - /> -
-
- ( - - )} - /> -
-
- { - if (watch("password") != val) { - return "Your passwords do no match"; - } - }, - }} - render={({ field: { value, onChange, ref } }) => ( - - )} - /> -
-
- - - Already have an account? Sign in. - - -
-
- -
-
- - ); -}; diff --git a/web/components/account/index.ts b/web/components/account/index.ts index 70b496c9afb..0d1cffbc661 100644 --- a/web/components/account/index.ts +++ b/web/components/account/index.ts @@ -1,6 +1,4 @@ +export * from "./o-auth"; export * from "./sign-in-forms"; export * from "./sign-up-forms"; export * from "./deactivate-account-modal"; -export * from "./github-sign-in"; -export * from "./google-sign-in"; -export * from "./email-signup-form"; diff --git a/web/components/account/github-sign-in.tsx b/web/components/account/o-auth/github-sign-in.tsx similarity index 100% rename from web/components/account/github-sign-in.tsx rename to web/components/account/o-auth/github-sign-in.tsx diff --git a/web/components/account/google-sign-in.tsx b/web/components/account/o-auth/google-sign-in.tsx similarity index 100% rename from web/components/account/google-sign-in.tsx rename to web/components/account/o-auth/google-sign-in.tsx diff --git a/web/components/account/o-auth/index.ts b/web/components/account/o-auth/index.ts new file mode 100644 index 00000000000..4cea6ce5b97 --- /dev/null +++ b/web/components/account/o-auth/index.ts @@ -0,0 +1,3 @@ +export * from "./github-sign-in"; +export * from "./google-sign-in"; +export * from "./o-auth-options"; diff --git a/web/components/account/sign-in-forms/o-auth-options.tsx b/web/components/account/o-auth/o-auth-options.tsx similarity index 100% rename from web/components/account/sign-in-forms/o-auth-options.tsx rename to web/components/account/o-auth/o-auth-options.tsx diff --git a/web/components/account/sign-in-forms/email-form.tsx b/web/components/account/sign-in-forms/email.tsx similarity index 100% rename from web/components/account/sign-in-forms/email-form.tsx rename to web/components/account/sign-in-forms/email.tsx diff --git a/web/components/account/sign-in-forms/index.ts b/web/components/account/sign-in-forms/index.ts index 9eb78b78d51..f84d41abc2d 100644 --- a/web/components/account/sign-in-forms/index.ts +++ b/web/components/account/sign-in-forms/index.ts @@ -1,5 +1,4 @@ -export * from "./email-form"; -export * from "./o-auth-options"; +export * from "./email"; export * from "./optional-set-password"; export * from "./password"; export * from "./root"; diff --git a/web/components/account/sign-up-forms/email-form.tsx b/web/components/account/sign-up-forms/email.tsx similarity index 100% rename from web/components/account/sign-up-forms/email-form.tsx rename to web/components/account/sign-up-forms/email.tsx diff --git a/web/components/account/sign-up-forms/index.ts b/web/components/account/sign-up-forms/index.ts index 256e85230c8..f84d41abc2d 100644 --- a/web/components/account/sign-up-forms/index.ts +++ b/web/components/account/sign-up-forms/index.ts @@ -1,4 +1,4 @@ -export * from "./email-form"; +export * from "./email"; export * from "./optional-set-password"; export * from "./password"; export * from "./root"; diff --git a/web/components/account/sign-up-forms/root.tsx b/web/components/account/sign-up-forms/root.tsx index 5777c1ab37f..6b3ae4d194a 100644 --- a/web/components/account/sign-up-forms/root.tsx +++ b/web/components/account/sign-up-forms/root.tsx @@ -22,7 +22,7 @@ export enum ESignUpSteps { export const SignUpRoot = observer(() => { // states - const [signInStep, setSignInStep] = useState(ESignUpSteps.PASSWORD); + const [signInStep, setSignInStep] = useState(ESignUpSteps.EMAIL); const [email, setEmail] = useState(""); // sign in redirection hook const { handleRedirection } = useSignInRedirection(); From 77d9ab0942c88ce9497f072a3cddb18050c2d3d2 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Fri, 19 Jan 2024 15:18:28 +0530 Subject: [PATCH 4/9] chore: forgot password workflow --- web/pages/accounts/forgot-password.tsx | 138 ++++++++++++++++++ .../{password.tsx => reset-password.tsx} | 84 ++++------- 2 files changed, 163 insertions(+), 59 deletions(-) create mode 100644 web/pages/accounts/forgot-password.tsx rename web/pages/accounts/{password.tsx => reset-password.tsx} (59%) diff --git a/web/pages/accounts/forgot-password.tsx b/web/pages/accounts/forgot-password.tsx new file mode 100644 index 00000000000..8974947650a --- /dev/null +++ b/web/pages/accounts/forgot-password.tsx @@ -0,0 +1,138 @@ +import { ReactElement } from "react"; +import Image from "next/image"; +import { useRouter } from "next/router"; +import { Controller, useForm } from "react-hook-form"; +// services +import { AuthService } from "services/auth.service"; +// hooks +import useToast from "hooks/use-toast"; +import useTimer from "hooks/use-timer"; +// layouts +import DefaultLayout from "layouts/default-layout"; +// components +import { LatestFeatureBlock } from "components/common"; +// ui +import { Button, Input } from "@plane/ui"; +// images +import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png"; +// helpers +import { checkEmailValidity } from "helpers/string.helper"; +// type +import { NextPageWithLayout } from "lib/types"; + +type TForgotPasswordFormValues = { + email: string; +}; + +const defaultValues: TForgotPasswordFormValues = { + email: "", +}; + +// services +const authService = new AuthService(); + +const ForgotPasswordPage: NextPageWithLayout = () => { + // router + const router = useRouter(); + const { email } = router.query; + // toast + const { setToastAlert } = useToast(); + // timer + const { timer: resendTimerCode, setTimer: setResendCodeTimer } = useTimer(0); + // form info + const { + control, + formState: { errors, isSubmitting, isValid }, + handleSubmit, + } = useForm({ + defaultValues: { + ...defaultValues, + email: email?.toString() ?? "", + }, + }); + + const handleForgotPassword = async (formData: TForgotPasswordFormValues) => { + await authService + .sendResetPasswordLink({ + email: formData.email, + }) + .then(() => { + setToastAlert({ + type: "success", + title: "Email sent", + message: + "Check your inbox for a link to reset your password. If it doesn't appear within a few minutes, check your spam folder.", + }); + setResendCodeTimer(30); + }) + .catch((err) => + setToastAlert({ + type: "error", + title: "Error!", + message: err?.error ?? "Something went wrong. Please try again.", + }) + ); + }; + + return ( +
+
+
+ Plane Logo + Plane +
+
+ +
+
+
+

+ Get on your flight deck +

+

Get a link to reset your password

+
+ checkEmailValidity(value) || "Email is invalid", + }} + render={({ field: { value, onChange, ref } }) => ( + + )} + /> + + +
+ +
+
+
+ ); +}; + +ForgotPasswordPage.getLayout = function getLayout(page: ReactElement) { + return {page}; +}; + +export default ForgotPasswordPage; diff --git a/web/pages/accounts/password.tsx b/web/pages/accounts/reset-password.tsx similarity index 59% rename from web/pages/accounts/password.tsx rename to web/pages/accounts/reset-password.tsx index 364c9271166..c065a880dc8 100644 --- a/web/pages/accounts/password.tsx +++ b/web/pages/accounts/reset-password.tsx @@ -1,9 +1,6 @@ import { ReactElement } from "react"; import Image from "next/image"; -import Link from "next/link"; import { useRouter } from "next/router"; -import { useTheme } from "next-themes"; -import { Lightbulb } from "lucide-react"; import { Controller, useForm } from "react-hook-form"; // services import { AuthService } from "services/auth.service"; @@ -12,11 +9,12 @@ import useToast from "hooks/use-toast"; import useSignInRedirection from "hooks/use-sign-in-redirection"; // layouts import DefaultLayout from "layouts/default-layout"; +// components +import { LatestFeatureBlock } from "components/common"; // ui import { Button, Input } from "@plane/ui"; // images import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png"; -import latestFeatures from "public/onboarding/onboarding-pages.svg"; // helpers import { checkEmailValidity } from "helpers/string.helper"; // type @@ -35,12 +33,10 @@ const defaultValues: TResetPasswordFormValues = { // services const authService = new AuthService(); -const HomePage: NextPageWithLayout = () => { +const ResetPasswordPage: NextPageWithLayout = () => { // router const router = useRouter(); const { uidb64, token, email } = router.query; - // next-themes - const { resolvedTheme } = useTheme(); // toast const { setToastAlert } = useToast(); // sign in redirection hook @@ -114,29 +110,24 @@ const HomePage: NextPageWithLayout = () => { /> )} /> -
- ( - - )} - /> -

- Whatever you choose now will be your account{"'"}s password until you change it. -

-
+ ( + + )} + /> -

- When you click the button above, you agree with our{" "} - - terms and conditions of service. - -

-
- -

- Try the latest features, like Tiptap editor, to write compelling responses.{" "} - - See new features - -

-
-
-
- Plane Issues -
-
+
); }; -HomePage.getLayout = function getLayout(page: ReactElement) { +ResetPasswordPage.getLayout = function getLayout(page: ReactElement) { return {page}; }; -export default HomePage; +export default ResetPasswordPage; From abc0a80634ba0e3e694779f9c3c6e126001d47ab Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Fri, 19 Jan 2024 15:41:53 +0530 Subject: [PATCH 5/9] refactor: form component props --- .../account/sign-in-forms/email.tsx | 17 ++------- .../sign-in-forms/optional-set-password.tsx | 3 -- .../account/sign-in-forms/password.tsx | 18 ++++----- web/components/account/sign-in-forms/root.tsx | 37 +++++++++++-------- .../account/sign-in-forms/unique-code.tsx | 10 ++--- .../account/sign-up-forms/email.tsx | 17 ++------- .../account/sign-up-forms/password.tsx | 14 +++---- web/components/account/sign-up-forms/root.tsx | 36 ++++++++++++++---- .../account/sign-up-forms/unique-code.tsx | 13 +++---- 9 files changed, 79 insertions(+), 86 deletions(-) diff --git a/web/components/account/sign-in-forms/email.tsx b/web/components/account/sign-in-forms/email.tsx index ed28572b45d..86e0ec12f4b 100644 --- a/web/components/account/sign-in-forms/email.tsx +++ b/web/components/account/sign-in-forms/email.tsx @@ -6,18 +6,15 @@ import { observer } from "mobx-react-lite"; import { AuthService } from "services/auth.service"; // hooks import useToast from "hooks/use-toast"; -import { useApplication } from "hooks/store"; // ui import { Button, Input } from "@plane/ui"; // helpers import { checkEmailValidity } from "helpers/string.helper"; // types import { IEmailCheckData } from "@plane/types"; -// constants -import { ESignInSteps } from "components/account"; type Props = { - handleStepChange: (step: ESignInSteps) => void; + onSubmit: (isPasswordAutoset: boolean) => void; updateEmail: (email: string) => void; }; @@ -28,12 +25,9 @@ type TEmailFormValues = { const authService = new AuthService(); export const SignInEmailForm: React.FC = observer((props) => { - const { handleStepChange, updateEmail } = props; + const { onSubmit, updateEmail } = props; // hooks const { setToastAlert } = useToast(); - const { - config: { envConfig }, - } = useApplication(); const { control, formState: { errors, isSubmitting, isValid }, @@ -56,12 +50,7 @@ export const SignInEmailForm: React.FC = observer((props) => { await authService .emailCheck(payload) - .then((res) => { - // if the password has been auto set, send the user to magic sign-in - if (envConfig?.is_smtp_configured && res.is_password_autoset) handleStepChange(ESignInSteps.UNIQUE_CODE); - // if the password has not been auto set, send them to password sign-in - else handleStepChange(ESignInSteps.PASSWORD); - }) + .then((res) => onSubmit(res.is_password_autoset)) .catch((err) => setToastAlert({ type: "error", diff --git a/web/components/account/sign-in-forms/optional-set-password.tsx b/web/components/account/sign-in-forms/optional-set-password.tsx index c88d4fd34c7..924b97e48c3 100644 --- a/web/components/account/sign-in-forms/optional-set-password.tsx +++ b/web/components/account/sign-in-forms/optional-set-password.tsx @@ -8,12 +8,9 @@ import useToast from "hooks/use-toast"; import { Button, Input } from "@plane/ui"; // helpers import { checkEmailValidity } from "helpers/string.helper"; -// constants -import { ESignInSteps } from "components/account"; type Props = { email: string; - handleStepChange: (step: ESignInSteps) => void; handleSignInRedirection: () => Promise; }; diff --git a/web/components/account/sign-in-forms/password.tsx b/web/components/account/sign-in-forms/password.tsx index f6629ffa1bc..e7fb8ea1618 100644 --- a/web/components/account/sign-in-forms/password.tsx +++ b/web/components/account/sign-in-forms/password.tsx @@ -19,10 +19,9 @@ import { ESignInSteps } from "components/account"; type Props = { email: string; - updateEmail: (email: string) => void; handleStepChange: (step: ESignInSteps) => void; - handleSignInRedirection: () => Promise; handleEmailClear: () => void; + onSubmit: () => Promise; }; type TPasswordFormValues = { @@ -38,7 +37,7 @@ const defaultValues: TPasswordFormValues = { const authService = new AuthService(); export const SignInPasswordForm: React.FC = observer((props) => { - const { email, updateEmail, handleStepChange, handleSignInRedirection, handleEmailClear } = props; + const { email, handleStepChange, handleEmailClear, onSubmit } = props; // states const [isSendingUniqueCode, setIsSendingUniqueCode] = useState(false); // toast alert @@ -49,7 +48,7 @@ export const SignInPasswordForm: React.FC = observer((props) => { // form info const { control, - formState: { dirtyFields, errors, isSubmitting, isValid }, + formState: { errors, isSubmitting, isValid }, getValues, handleSubmit, setError, @@ -64,8 +63,6 @@ export const SignInPasswordForm: React.FC = observer((props) => { }); const handleFormSubmit = async (formData: TPasswordFormValues) => { - updateEmail(formData.email); - const payload: IPasswordSignInData = { email: formData.email, password: formData.password, @@ -73,7 +70,7 @@ export const SignInPasswordForm: React.FC = observer((props) => { await authService .passwordSignIn(payload) - .then(async () => await handleSignInRedirection()) + .then(async () => await onSubmit()) .catch((err) => setToastAlert({ type: "error", @@ -157,7 +154,7 @@ export const SignInPasswordForm: React.FC = observer((props) => { control={control} name="password" rules={{ - required: dirtyFields.email ? false : "Password is required", + required: "Password is required", }} render={({ field: { value, onChange } }) => ( = observer((props) => { )} />
- + Forgot your password?
diff --git a/web/components/account/sign-in-forms/root.tsx b/web/components/account/sign-in-forms/root.tsx index dd56396503e..7f594b62460 100644 --- a/web/components/account/sign-in-forms/root.tsx +++ b/web/components/account/sign-in-forms/root.tsx @@ -33,6 +33,23 @@ export const SignInRoot = observer(() => { config: { envConfig }, } = useApplication(); + // step 1 submit handler- email verification + const handleEmailVerification = (isPasswordAutoset: boolean) => { + if (envConfig?.is_smtp_configured && isPasswordAutoset) setSignInStep(ESignInSteps.UNIQUE_CODE); + else setSignInStep(ESignInSteps.PASSWORD); + }; + + // step 2 submit handler- unique code sign in + const handleUniqueCodeSignIn = async (isPasswordAutoset: boolean) => { + if (isPasswordAutoset) setSignInStep(ESignInSteps.OPTIONAL_SET_PASSWORD); + else await handleRedirection(); + }; + + // step 3 submit handler- password sign in + const handlePasswordSignIn = async () => { + await handleRedirection(); + }; + const isOAuthEnabled = envConfig && (envConfig.google_client_id || envConfig.github_client_id); return ( @@ -40,53 +57,43 @@ export const SignInRoot = observer(() => {
<> {signInStep === ESignInSteps.EMAIL && ( - setSignInStep(step)} - updateEmail={(newEmail) => setEmail(newEmail)} - /> + setEmail(newEmail)} /> )} {signInStep === ESignInSteps.UNIQUE_CODE && ( setSignInStep(step)} - handleSignInRedirection={handleRedirection} handleEmailClear={() => { setEmail(""); setSignInStep(ESignInSteps.EMAIL); }} + onSubmit={handleUniqueCodeSignIn} submitButtonText="Continue" /> )} {signInStep === ESignInSteps.PASSWORD && ( setEmail(newEmail)} + onSubmit={handlePasswordSignIn} handleStepChange={(step) => setSignInStep(step)} handleEmailClear={() => { setEmail(""); setSignInStep(ESignInSteps.EMAIL); }} - handleSignInRedirection={handleRedirection} /> )} {signInStep === ESignInSteps.USE_UNIQUE_CODE_FROM_PASSWORD && ( setSignInStep(step)} - handleSignInRedirection={handleRedirection} handleEmailClear={() => { setEmail(""); setSignInStep(ESignInSteps.EMAIL); }} + onSubmit={handleUniqueCodeSignIn} submitButtonText="Go to workspace" /> )} {signInStep === ESignInSteps.OPTIONAL_SET_PASSWORD && ( - setSignInStep(step)} - handleSignInRedirection={handleRedirection} - /> + )}
diff --git a/web/components/account/sign-in-forms/unique-code.tsx b/web/components/account/sign-in-forms/unique-code.tsx index 18540efd6c5..4697d899ee8 100644 --- a/web/components/account/sign-in-forms/unique-code.tsx +++ b/web/components/account/sign-in-forms/unique-code.tsx @@ -13,13 +13,10 @@ import { Button, Input } from "@plane/ui"; import { checkEmailValidity } from "helpers/string.helper"; // types import { IEmailCheckData, IMagicSignInData } from "@plane/types"; -// constants -import { ESignInSteps } from "components/account"; type Props = { email: string; - handleStepChange: (step: ESignInSteps) => void; - handleSignInRedirection: () => Promise; + onSubmit: (isPasswordAutoset: boolean) => Promise; handleEmailClear: () => void; submitButtonText: string; }; @@ -39,7 +36,7 @@ const authService = new AuthService(); const userService = new UserService(); export const SignInUniqueCodeForm: React.FC = (props) => { - const { email, handleStepChange, handleSignInRedirection, handleEmailClear, submitButtonText } = props; + const { email, onSubmit, handleEmailClear, submitButtonText } = props; // states const [isRequestingNewCode, setIsRequestingNewCode] = useState(false); // toast alert @@ -75,8 +72,7 @@ export const SignInUniqueCodeForm: React.FC = (props) => { .then(async () => { const currentUser = await userService.currentUser(); - if (currentUser.is_password_autoset) handleStepChange(ESignInSteps.OPTIONAL_SET_PASSWORD); - else await handleSignInRedirection(); + await onSubmit(currentUser.is_password_autoset); }) .catch((err) => setToastAlert({ diff --git a/web/components/account/sign-up-forms/email.tsx b/web/components/account/sign-up-forms/email.tsx index b57f672d0df..c413438be8b 100644 --- a/web/components/account/sign-up-forms/email.tsx +++ b/web/components/account/sign-up-forms/email.tsx @@ -6,18 +6,15 @@ import { observer } from "mobx-react-lite"; import { AuthService } from "services/auth.service"; // hooks import useToast from "hooks/use-toast"; -import { useApplication } from "hooks/store"; // ui import { Button, Input } from "@plane/ui"; // helpers import { checkEmailValidity } from "helpers/string.helper"; // types import { IEmailCheckData } from "@plane/types"; -// constants -import { ESignUpSteps } from "components/account"; type Props = { - handleStepChange: (step: ESignUpSteps) => void; + onSubmit: () => void; updateEmail: (email: string) => void; }; @@ -28,12 +25,9 @@ type TEmailFormValues = { const authService = new AuthService(); export const SignUpEmailForm: React.FC = observer((props) => { - const { handleStepChange, updateEmail } = props; + const { onSubmit, updateEmail } = props; // hooks const { setToastAlert } = useToast(); - const { - config: { envConfig }, - } = useApplication(); const { control, formState: { errors, isSubmitting, isValid }, @@ -56,12 +50,7 @@ export const SignUpEmailForm: React.FC = observer((props) => { await authService .emailCheck(payload) - .then(() => { - // if SMTP is configured, send the user to unique code form - if (envConfig?.is_smtp_configured) handleStepChange(ESignUpSteps.UNIQUE_CODE); - // if SMTP is not configured, send the user to password form - else handleStepChange(ESignUpSteps.PASSWORD); - }) + .then(() => onSubmit()) .catch((err) => setToastAlert({ type: "error", diff --git a/web/components/account/sign-up-forms/password.tsx b/web/components/account/sign-up-forms/password.tsx index 38151411678..a2f3fd8c3b7 100644 --- a/web/components/account/sign-up-forms/password.tsx +++ b/web/components/account/sign-up-forms/password.tsx @@ -16,8 +16,8 @@ import { IPasswordSignInData } from "@plane/types"; type Props = { email: string; - handleSignInRedirection: () => Promise; handleEmailClear: () => void; + onSubmit: () => Promise; }; type TPasswordFormValues = { @@ -33,13 +33,13 @@ const defaultValues: TPasswordFormValues = { const authService = new AuthService(); export const SignUpPasswordForm: React.FC = observer((props) => { - const { email, handleSignInRedirection, handleEmailClear } = props; + const { email, handleEmailClear, onSubmit } = props; // toast alert const { setToastAlert } = useToast(); // form info const { control, - formState: { dirtyFields, errors, isSubmitting, isValid }, + formState: { errors, isSubmitting, isValid }, handleSubmit, setFocus, } = useForm({ @@ -59,7 +59,7 @@ export const SignUpPasswordForm: React.FC = observer((props) => { await authService .passwordSignIn(payload) - .then(async () => await handleSignInRedirection()) + .then(async () => await onSubmit()) .catch((err) => setToastAlert({ type: "error", @@ -78,9 +78,7 @@ export const SignUpPasswordForm: React.FC = observer((props) => {

Moving to the runway

-

- Let{"'"}s set a password so you can do away with codes. -

+

Can{"'"}t wait to have you on board.

= observer((props) => { control={control} name="password" rules={{ - required: dirtyFields.email ? false : "Password is required", + required: "Password is required", }} render={({ field: { value, onChange } }) => ( { config: { envConfig }, } = useApplication(); + // step 1 submit handler- email verification + const handleEmailVerification = () => { + if (envConfig?.is_smtp_configured) setSignInStep(ESignUpSteps.UNIQUE_CODE); + else setSignInStep(ESignUpSteps.PASSWORD); + }; + + // step 2 submit handler- unique code sign in + const handleUniqueCodeSignIn = async (isPasswordAutoset: boolean) => { + if (isPasswordAutoset) setSignInStep(ESignUpSteps.OPTIONAL_SET_PASSWORD); + else await handleRedirection(); + }; + + // step 3 submit handler- password sign in + const handlePasswordSignIn = async () => { + await handleRedirection(); + }; + const isOAuthEnabled = envConfig && (envConfig.google_client_id || envConfig.github_client_id); return ( @@ -38,23 +55,26 @@ export const SignUpRoot = observer(() => {
<> {signInStep === ESignUpSteps.EMAIL && ( - setSignInStep(step)} - updateEmail={(newEmail) => setEmail(newEmail)} - /> + setEmail(newEmail)} /> )} {signInStep === ESignUpSteps.UNIQUE_CODE && ( setSignInStep(step)} + handleEmailClear={() => { + setEmail(""); + setSignInStep(ESignUpSteps.EMAIL); + }} + onSubmit={handleUniqueCodeSignIn} /> )} {signInStep === ESignUpSteps.PASSWORD && ( setSignInStep(ESignUpSteps.EMAIL)} - handleSignInRedirection={handleRedirection} + handleEmailClear={() => { + setEmail(""); + setSignInStep(ESignUpSteps.EMAIL); + }} + onSubmit={handlePasswordSignIn} /> )} {signInStep === ESignUpSteps.OPTIONAL_SET_PASSWORD && ( diff --git a/web/components/account/sign-up-forms/unique-code.tsx b/web/components/account/sign-up-forms/unique-code.tsx index c8d571aeedc..a97aa8a251d 100644 --- a/web/components/account/sign-up-forms/unique-code.tsx +++ b/web/components/account/sign-up-forms/unique-code.tsx @@ -14,13 +14,11 @@ import { Button, Input } from "@plane/ui"; import { checkEmailValidity } from "helpers/string.helper"; // types import { IEmailCheckData, IMagicSignInData } from "@plane/types"; -// constants -import { ESignUpSteps } from "components/account"; type Props = { email: string; - handleStepChange: (step: ESignUpSteps) => void; - handleSignInRedirection: () => Promise; + handleEmailClear: () => void; + onSubmit: (isPasswordAutoset: boolean) => Promise; }; type TUniqueCodeFormValues = { @@ -38,7 +36,7 @@ const authService = new AuthService(); const userService = new UserService(); export const SignUpUniqueCodeForm: React.FC = (props) => { - const { email, handleStepChange, handleSignInRedirection } = props; + const { email, handleEmailClear, onSubmit } = props; // states const [isRequestingNewCode, setIsRequestingNewCode] = useState(false); // toast alert @@ -74,8 +72,7 @@ export const SignUpUniqueCodeForm: React.FC = (props) => { .then(async () => { const currentUser = await userService.currentUser(); - if (currentUser.is_password_autoset) handleStepChange(ESignUpSteps.OPTIONAL_SET_PASSWORD); - else await handleSignInRedirection(); + await onSubmit(currentUser.is_password_autoset); }) .catch((err) => setToastAlert({ @@ -162,7 +159,7 @@ export const SignUpUniqueCodeForm: React.FC = (props) => { {value.length > 0 && ( handleStepChange(ESignUpSteps.EMAIL)} + onClick={handleEmailClear} /> )}
From 84959345c11c73bb689f17470cd4666581e7ea66 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Fri, 19 Jan 2024 17:13:25 +0530 Subject: [PATCH 6/9] chore: forgot password popover for instances with smtp unconfigured --- .../account/o-auth/github-sign-in.tsx | 5 +- .../account/o-auth/google-sign-in.tsx | 7 +-- .../account/o-auth/o-auth-options.tsx | 7 +-- .../sign-in-forms/forgot-password-popover.tsx | 54 +++++++++++++++++++ web/components/account/sign-in-forms/index.ts | 1 + .../account/sign-in-forms/password.tsx | 20 ++++--- web/components/account/sign-in-forms/root.tsx | 2 +- web/components/account/sign-up-forms/root.tsx | 2 +- 8 files changed, 80 insertions(+), 18 deletions(-) create mode 100644 web/components/account/sign-in-forms/forgot-password-popover.tsx diff --git a/web/components/account/o-auth/github-sign-in.tsx b/web/components/account/o-auth/github-sign-in.tsx index 27a8bf01c82..74bfd6d940f 100644 --- a/web/components/account/o-auth/github-sign-in.tsx +++ b/web/components/account/o-auth/github-sign-in.tsx @@ -12,10 +12,11 @@ import githubDarkModeImage from "/public/logos/github-dark.svg"; type Props = { handleSignIn: React.Dispatch; clientId: string; + type: "sign_in" | "sign_up"; }; export const GitHubSignInButton: FC = (props) => { - const { handleSignIn, clientId } = props; + const { handleSignIn, clientId, type } = props; // states const [loginCallBackURL, setLoginCallBackURL] = useState(undefined); const [gitCode, setGitCode] = useState(null); @@ -53,7 +54,7 @@ export const GitHubSignInButton: FC = (props) => { width={20} alt="GitHub Logo" /> - Sign-in with GitHub + {type === "sign_in" ? "Sign-in" : "Sign-up"} with GitHub
diff --git a/web/components/account/o-auth/google-sign-in.tsx b/web/components/account/o-auth/google-sign-in.tsx index 48488e07e1d..93958bbd243 100644 --- a/web/components/account/o-auth/google-sign-in.tsx +++ b/web/components/account/o-auth/google-sign-in.tsx @@ -4,10 +4,11 @@ import Script from "next/script"; type Props = { handleSignIn: React.Dispatch; clientId: string; + type: "sign_in" | "sign_up"; }; export const GoogleSignInButton: FC = (props) => { - const { handleSignIn, clientId } = props; + const { handleSignIn, clientId, type } = props; // refs const googleSignInButton = useRef(null); // states @@ -29,7 +30,7 @@ export const GoogleSignInButton: FC = (props) => { theme: "outline", size: "large", logo_alignment: "center", - text: "signin_with", + text: type === "sign_in" ? "signin_with" : "signup_with", width: 384, } as GsiButtonConfiguration // customization attributes ); @@ -40,7 +41,7 @@ export const GoogleSignInButton: FC = (props) => { window?.google?.accounts.id.prompt(); // also display the One Tap dialog setGsiScriptLoaded(true); - }, [handleSignIn, gsiScriptLoaded, clientId]); + }, [handleSignIn, gsiScriptLoaded, clientId, type]); useEffect(() => { if (window?.google?.accounts?.id) { diff --git a/web/components/account/o-auth/o-auth-options.tsx b/web/components/account/o-auth/o-auth-options.tsx index 610d1ac590b..3ad1c2fa19a 100644 --- a/web/components/account/o-auth/o-auth-options.tsx +++ b/web/components/account/o-auth/o-auth-options.tsx @@ -9,13 +9,14 @@ import { GitHubSignInButton, GoogleSignInButton } from "components/account"; type Props = { handleSignInRedirection: () => Promise; + type: "sign_in" | "sign_up"; }; // services const authService = new AuthService(); export const OAuthOptions: React.FC = observer((props) => { - const { handleSignInRedirection } = props; + const { handleSignInRedirection, type } = props; // toast alert const { setToastAlert } = useToast(); // mobx store @@ -75,11 +76,11 @@ export const OAuthOptions: React.FC = observer((props) => {
{envConfig?.google_client_id && (
- +
)} {envConfig?.github_client_id && ( - + )}
diff --git a/web/components/account/sign-in-forms/forgot-password-popover.tsx b/web/components/account/sign-in-forms/forgot-password-popover.tsx new file mode 100644 index 00000000000..8be3e8eabe1 --- /dev/null +++ b/web/components/account/sign-in-forms/forgot-password-popover.tsx @@ -0,0 +1,54 @@ +import { Fragment, useState } from "react"; +import { usePopper } from "react-popper"; +import { Popover } from "@headlessui/react"; +import { X } from "lucide-react"; + +export const ForgotPasswordPopover = () => { + // popper-js refs + const [referenceElement, setReferenceElement] = useState(null); + const [popperElement, setPopperElement] = useState(null); + // popper-js init + const { styles, attributes } = usePopper(referenceElement, popperElement, { + placement: "right-start", + modifiers: [ + { + name: "preventOverflow", + options: { + padding: 12, + }, + }, + ], + }); + + return ( + + + + + + {({ close }) => ( +
+ 🤥 +

+ We see that your god hasn{"'"}t enabled SMTP, we will not be able to send a password reset link +

+ +
+ )} +
+
+ ); +}; diff --git a/web/components/account/sign-in-forms/index.ts b/web/components/account/sign-in-forms/index.ts index f84d41abc2d..8e44f490bcc 100644 --- a/web/components/account/sign-in-forms/index.ts +++ b/web/components/account/sign-in-forms/index.ts @@ -1,4 +1,5 @@ export * from "./email"; +export * from "./forgot-password-popover"; export * from "./optional-set-password"; export * from "./password"; export * from "./root"; diff --git a/web/components/account/sign-in-forms/password.tsx b/web/components/account/sign-in-forms/password.tsx index e7fb8ea1618..8d2596439f3 100644 --- a/web/components/account/sign-in-forms/password.tsx +++ b/web/components/account/sign-in-forms/password.tsx @@ -8,14 +8,14 @@ import { AuthService } from "services/auth.service"; // hooks import useToast from "hooks/use-toast"; import { useApplication } from "hooks/store"; +// components +import { ESignInSteps, ForgotPasswordPopover } from "components/account"; // ui import { Button, Input } from "@plane/ui"; // helpers import { checkEmailValidity } from "helpers/string.helper"; // types import { IPasswordSignInData } from "@plane/types"; -// constants -import { ESignInSteps } from "components/account"; type Props = { email: string; @@ -168,12 +168,16 @@ export const SignInPasswordForm: React.FC = observer((props) => { )} />
- - Forgot your password? - + {envConfig?.is_smtp_configured ? ( + + Forgot your password? + + ) : ( + + )}
diff --git a/web/components/account/sign-in-forms/root.tsx b/web/components/account/sign-in-forms/root.tsx index 7f594b62460..86d0d4b703b 100644 --- a/web/components/account/sign-in-forms/root.tsx +++ b/web/components/account/sign-in-forms/root.tsx @@ -99,7 +99,7 @@ export const SignInRoot = observer(() => {
{isOAuthEnabled && signInStep === ESignInSteps.EMAIL && ( <> - +

Don{"'"}t have an account?{" "} diff --git a/web/components/account/sign-up-forms/root.tsx b/web/components/account/sign-up-forms/root.tsx index e5e16e0e011..f2d000881f6 100644 --- a/web/components/account/sign-up-forms/root.tsx +++ b/web/components/account/sign-up-forms/root.tsx @@ -88,7 +88,7 @@ export const SignUpRoot = observer(() => {

{isOAuthEnabled && signInStep === ESignUpSteps.EMAIL && ( <> - +

Already using Plane?{" "} From 47eb20442543288c745d7824de9f5b0b550589fb Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Fri, 19 Jan 2024 18:07:57 +0530 Subject: [PATCH 7/9] chore: updated UX copy --- .../account/sign-in-forms/email.tsx | 2 +- .../sign-in-forms/forgot-password-popover.tsx | 2 +- .../sign-in-forms/optional-set-password.tsx | 2 +- .../account/sign-in-forms/password.tsx | 11 +++-- web/components/account/sign-in-forms/root.tsx | 40 +++++++++++-------- .../account/sign-in-forms/unique-code.tsx | 4 +- .../sign-up-forms/optional-set-password.tsx | 6 ++- .../account/sign-up-forms/password.tsx | 20 +++++----- web/components/account/sign-up-forms/root.tsx | 29 ++++++-------- .../account/sign-up-forms/unique-code.tsx | 4 +- 10 files changed, 66 insertions(+), 54 deletions(-) diff --git a/web/components/account/sign-in-forms/email.tsx b/web/components/account/sign-in-forms/email.tsx index 86e0ec12f4b..dd3d7ede75f 100644 --- a/web/components/account/sign-in-forms/email.tsx +++ b/web/components/account/sign-in-forms/email.tsx @@ -66,7 +66,7 @@ export const SignInEmailForm: React.FC = observer((props) => { Welcome back, let{"'"}s get you on board

- Get back to your issues, projects and workspaces + Get back to your issues, projects and workspaces.

diff --git a/web/components/account/sign-in-forms/forgot-password-popover.tsx b/web/components/account/sign-in-forms/forgot-password-popover.tsx index 8be3e8eabe1..d652e51f1fe 100644 --- a/web/components/account/sign-in-forms/forgot-password-popover.tsx +++ b/web/components/account/sign-in-forms/forgot-password-popover.tsx @@ -34,7 +34,7 @@ export const ForgotPasswordPopover = () => { {({ close }) => (
= (props) => { <>

Set your password

- If you{"'"}d like to do away with codes, set a password here + If you{"'"}d like to do away with codes, set a password here.

= observer((props) => { const { config: { envConfig }, } = useApplication(); + // derived values + const isSmtpConfigured = envConfig?.is_smtp_configured; // form info const { control, @@ -137,12 +139,15 @@ export const SignInPasswordForm: React.FC = observer((props) => { hasError={Boolean(errors.email)} placeholder="orville.wright@frstflt.com" className="h-[46px] w-full border border-onboarding-border-100 pr-12 placeholder:text-onboarding-text-400" - disabled + disabled={isSmtpConfigured} /> {value.length > 0 && ( { + if (isSmtpConfigured) handleEmailClear(); + else onChange(""); + }} /> )}
@@ -168,7 +173,7 @@ export const SignInPasswordForm: React.FC = observer((props) => { )} />
- {envConfig?.is_smtp_configured ? ( + {isSmtpConfigured ? ( { // states - const [signInStep, setSignInStep] = useState(ESignInSteps.EMAIL); + const [signInStep, setSignInStep] = useState(null); const [email, setEmail] = useState(""); // sign in redirection hook const { handleRedirection } = useSignInRedirection(); @@ -32,10 +32,12 @@ export const SignInRoot = observer(() => { const { config: { envConfig }, } = useApplication(); + // derived values + const isSmtpConfigured = envConfig?.is_smtp_configured; // step 1 submit handler- email verification const handleEmailVerification = (isPasswordAutoset: boolean) => { - if (envConfig?.is_smtp_configured && isPasswordAutoset) setSignInStep(ESignInSteps.UNIQUE_CODE); + if (isSmtpConfigured && isPasswordAutoset) setSignInStep(ESignInSteps.UNIQUE_CODE); else setSignInStep(ESignInSteps.PASSWORD); }; @@ -52,6 +54,11 @@ export const SignInRoot = observer(() => { const isOAuthEnabled = envConfig && (envConfig.google_client_id || envConfig.github_client_id); + useEffect(() => { + if (isSmtpConfigured) setSignInStep(ESignInSteps.EMAIL); + else setSignInStep(ESignInSteps.PASSWORD); + }, [isSmtpConfigured]); + return ( <>
@@ -73,12 +80,12 @@ export const SignInRoot = observer(() => { {signInStep === ESignInSteps.PASSWORD && ( setSignInStep(step)} handleEmailClear={() => { setEmail(""); setSignInStep(ESignInSteps.EMAIL); }} + onSubmit={handlePasswordSignIn} + handleStepChange={(step) => setSignInStep(step)} /> )} {signInStep === ESignInSteps.USE_UNIQUE_CODE_FROM_PASSWORD && ( @@ -97,17 +104,18 @@ export const SignInRoot = observer(() => { )}
- {isOAuthEnabled && signInStep === ESignInSteps.EMAIL && ( - <> - -

- Don{"'"}t have an account?{" "} - - Sign up - -

- - )} + {isOAuthEnabled && + (signInStep === ESignInSteps.EMAIL || (!isSmtpConfigured && signInStep === ESignInSteps.PASSWORD)) && ( + <> + +

+ Don{"'"}t have an account?{" "} + + Sign up + +

+ + )} ); diff --git a/web/components/account/sign-in-forms/unique-code.tsx b/web/components/account/sign-in-forms/unique-code.tsx index 4697d899ee8..544c8c96fec 100644 --- a/web/components/account/sign-in-forms/unique-code.tsx +++ b/web/components/account/sign-in-forms/unique-code.tsx @@ -130,7 +130,9 @@ export const SignInUniqueCodeForm: React.FC = (props) => { <>

Moving to the runway

- Paste the code you got at {email} below. + Paste the code you got at +
+ {email} below.

diff --git a/web/components/account/sign-up-forms/optional-set-password.tsx b/web/components/account/sign-up-forms/optional-set-password.tsx index ecd4f82a864..104dbcd6605 100644 --- a/web/components/account/sign-up-forms/optional-set-password.tsx +++ b/web/components/account/sign-up-forms/optional-set-password.tsx @@ -89,7 +89,9 @@ export const SignUpOptionalSetPasswordForm: React.FC = (props) => { <>

Moving to the runway

- Let{"'"}s set a password so you can do away with codes. + Let{"'"}s set a password so +
+ you can do away with codes.

= (props) => { )} />

- This password will continue to be your account{"'"}s password + This password will continue to be your account{"'"}s password.

diff --git a/web/components/account/sign-up-forms/password.tsx b/web/components/account/sign-up-forms/password.tsx index a2f3fd8c3b7..5b5b5573476 100644 --- a/web/components/account/sign-up-forms/password.tsx +++ b/web/components/account/sign-up-forms/password.tsx @@ -15,8 +15,6 @@ import { checkEmailValidity } from "helpers/string.helper"; import { IPasswordSignInData } from "@plane/types"; type Props = { - email: string; - handleEmailClear: () => void; onSubmit: () => Promise; }; @@ -33,7 +31,7 @@ const defaultValues: TPasswordFormValues = { const authService = new AuthService(); export const SignUpPasswordForm: React.FC = observer((props) => { - const { email, handleEmailClear, onSubmit } = props; + const { onSubmit } = props; // toast alert const { setToastAlert } = useToast(); // form info @@ -45,7 +43,6 @@ export const SignUpPasswordForm: React.FC = observer((props) => { } = useForm({ defaultValues: { ...defaultValues, - email, }, mode: "onChange", reValidateMode: "onChange", @@ -75,10 +72,12 @@ export const SignUpPasswordForm: React.FC = observer((props) => { return ( <> -

- Moving to the runway +

+ Get on your flight deck

-

Can{"'"}t wait to have you on board.

+

+ Create or join a workspace. Start with your e-mail. +

= observer((props) => { hasError={Boolean(errors.email)} placeholder="orville.wright@frstflt.com" className="h-[46px] w-full border border-onboarding-border-100 pr-12 placeholder:text-onboarding-text-400" - disabled /> {value.length > 0 && ( onChange("")} /> )}
@@ -124,13 +122,13 @@ export const SignUpPasswordForm: React.FC = observer((props) => { value={value} onChange={onChange} hasError={Boolean(errors.password)} - placeholder="Choose 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" /> )} />

- This password will continue to be your account{"'"}s password + This password will continue to be your account{"'"}s password.