plane/web/components/account/email-code-form.tsx
Lakhan Baheti 3c89ef8cc3 fix: onboarding bugs & improvements (#2839)
* fix: terms & condition alignment

* fix: onboarding page scrolling

* fix: create workspace name clear

* fix: setup profile sidebar workspace name

* fix: invite team screen button text

* fix: inner div min height

* fix: allow single invite also in invite member

* fix: UI clipping in invite members

* fix: signin screen scroll

* fix: sidebar notification icon

* fix: sidebar project name & icon

* fix: user detail bottom image alignment

* fix: step indicator in invite member

* fix: try different account modal state

* fix: setup profile remove image

* fix: workspace slug clear

* fix: invite member UI & focus

* fix: step indicator size

* fix: inner div placement

* fix: invite member validation logic

* fix: cuurent user data persistency

* fix: sidebar animation colors

* feat: signup & resend

* fix: sign out theme persist from popover

* fix: imports

* chore: signin responsiveness

* fix: sign-in, sign-up top padding
2023-12-07 19:59:35 +05:30

315 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useEffect, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { XCircle } from "lucide-react";
// ui
import { Button, Input } from "@plane/ui";
// components
import { AuthType } from "components/page-views";
// services
import { AuthService } from "services/auth.service";
// hooks
import useToast from "hooks/use-toast";
import useTimer from "hooks/use-timer";
type EmailCodeFormValues = {
email: string;
key?: string;
token?: string;
};
const authService = new AuthService();
type Props = {
handleSignIn: any;
authType: AuthType;
};
export const EmailCodeForm: React.FC<Props> = (Props) => {
const { handleSignIn, authType } = Props;
// states
const [codeSent, setCodeSent] = useState(false);
const [codeResent, setCodeResent] = useState(false);
const [isCodeResending, setIsCodeResending] = useState(false);
const [errorResendingCode, setErrorResendingCode] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [sentEmail, setSentEmail] = useState<string>("");
const { setToastAlert } = useToast();
const { timer: resendCodeTimer, setTimer: setResendCodeTimer } = useTimer();
const {
handleSubmit,
control,
setError,
setValue,
getValues,
formState: { errors, isSubmitting, isValid, isDirty },
} = useForm<EmailCodeFormValues>({
defaultValues: {
email: "",
key: "",
token: "",
},
mode: "onChange",
reValidateMode: "onChange",
});
const isResendDisabled = resendCodeTimer > 0 || isCodeResending || isSubmitting;
const onSubmit = async ({ email }: EmailCodeFormValues) => {
setErrorResendingCode(false);
await authService
.emailCode({ email })
.then((res) => {
setSentEmail(email);
setValue("key", res.key);
setCodeSent(true);
})
.catch((err) => {
setErrorResendingCode(true);
setToastAlert({
title: "Oops!",
type: "error",
message: err?.error,
});
});
};
const handleSignin = async (formData: EmailCodeFormValues) => {
setIsLoading(true);
await authService
.magicSignIn(formData)
.then((response) => {
handleSignIn(response);
})
.catch((error) => {
setIsLoading(false);
setToastAlert({
title: "Oops!",
type: "error",
message: error?.response?.data?.error ?? "Enter the correct code to sign in",
});
setError("token" as keyof EmailCodeFormValues, {
type: "manual",
message: error?.error,
});
});
};
const emailOld = getValues("email");
useEffect(() => {
const submitForm = (e: KeyboardEvent) => {
if (!codeSent && e.key === "Enter") {
e.preventDefault();
handleSubmit(onSubmit)().then(() => {
setResendCodeTimer(30);
});
} else if (
codeSent &&
sentEmail != getValues("email") &&
getValues("email").length > 0 &&
(e.key === "Enter" || e.key === "Tab")
) {
e.preventDefault();
console.log("resend");
onSubmit({ email: getValues("email") }).then(() => {
setCodeResent(true);
});
}
};
window.addEventListener("keydown", submitForm);
return () => {
window.removeEventListener("keydown", submitForm);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [handleSubmit, codeSent, sentEmail]);
return (
<>
{codeSent || codeResent ? (
<>
<h1 className="text-center text-2xl sm:text-2.5xl font-semibold text-custom-text-100">
Moving to the runway
</h1>
<div className="text-center text-sm text-onboarding-text-200 mt-3">
<p>Paste the code you got at </p>
<span className="text-center text-sm text-custom-primary-80 mt-1 font-semibold ">{sentEmail} </span>
<span className="text-onboarding-text-200">below.</span>
</div>
</>
) : (
<>
<h1 className="text-center text-2xl sm:text-2.5xl font-semibold text-onboarding-text-100">
{authType === "sign-in" ? "Get on your flight deck!" : "Lets get you prepped!"}
</h1>
{authType == "sign-up" ? (
<div>
<p className="text-center text-sm text-onboarding-text-200 mt-3">
This whole thing will take less than two minutes.
</p>
<p className="text-center text-sm text-onboarding-text-200 mt-1">Promise!</p>
</div>
) : (
<p className="text-center text-sm text-onboarding-text-200 px-20 mt-3">
Sign in with the email you used to sign up for Plane
</p>
)}
</>
)}
<form className="mt-5 sm:w-96 mx-auto">
<div className="space-y-1">
<Controller
control={control}
name="email"
rules={{
required: "Email address is required",
validate: (value) =>
/^(([^<>()[\]\\.,;:\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 } }) => (
<div className={`flex items-center relative rounded-md bg-onboarding-background-200`}>
<Input
id="email"
name="email"
type="email"
value={value}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.email)}
placeholder="orville.wright@firstflight.com"
className={`w-full h-[46px] placeholder:text-onboarding-text-400 border border-onboarding-border-100 pr-12`}
/>
{value.length > 0 && (
<XCircle
className="h-5 w-5 absolute stroke-custom-text-400 hover:cursor-pointer right-3"
onClick={() => setValue("email", "")}
/>
)}
</div>
)}
/>
</div>
{codeSent && (
<>
<div>
{codeResent && sentEmail === getValues("email") ? (
<div className="text-sm my-2.5 text-onboarding-text-300 m-0">
You got a new code at <span className="font-semibold text-custom-primary-80">{sentEmail}</span>.
</div>
) : sentEmail != getValues("email") && getValues("email").length > 0 ? (
<div className="text-sm my-2.5 text-onboarding-text-300 m-0">
Hit enter
<span> </span>or <span className="italic">Tab</span> to get a new code
</div>
) : (
<div className="my-4" />
)}
</div>
<div className={`flex items-center relative rounded-md bg-onboarding-background-200`}>
<Controller
control={control}
name="token"
rules={{
required: "Code is required",
}}
render={({ field: { value, onChange, ref } }) => (
<Input
id="token"
name="token"
type="token"
value={value ?? ""}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.token)}
placeholder="gets-sets-flys"
className="border-onboarding-border-100 h-[46px] w-full"
/>
)}
/>
{resendCodeTimer <= 0 && !isResendDisabled && (
<button
type="button"
className={`flex absolute w-fit right-3.5 justify-end text-xs outline-none cursor-pointer text-custom-primary-100`}
onClick={() => {
setIsCodeResending(true);
onSubmit({ email: getValues("email") }).then(() => {
setCodeResent(true);
setIsCodeResending(false);
setResendCodeTimer(30);
});
}}
disabled={isResendDisabled}
>
<span className="font-medium">Resend</span>
</button>
)}
</div>
<div
className={`flex w-full justify-end text-xs outline-none ${
isResendDisabled ? "cursor-default text-custom-text-200" : "cursor-pointer text-custom-primary-100"
} `}
>
{resendCodeTimer > 0 ? (
<span className="text-right">Request new code in {resendCodeTimer}s</span>
) : isCodeResending ? (
"Sending new code..."
) : null}
</div>
</>
)}
{codeSent ? (
<div className="my-4">
{" "}
<Button
variant="primary"
type="submit"
className="w-full"
size="xl"
onClick={handleSubmit(handleSignin)}
disabled={!isValid && isDirty}
loading={isLoading}
>
{isLoading ? "Signing in..." : "Next step"}
</Button>
<div className="w-3/4 my-4 mx-auto">
<p className="text-xs text-center text-onboarding-text-300">
When you click the button above, you agree with our{" "}
<a
href="https://plane.so/terms-and-conditions"
target="_blank"
rel="noopener noreferrer"
className="font-medium underline"
>
terms and conditions of service.
</a>{" "}
</p>
</div>
</div>
) : (
<Button
variant="primary"
className="w-full mt-4"
size="xl"
onClick={() => {
handleSubmit(onSubmit)().then(() => {
setResendCodeTimer(30);
});
}}
disabled={!isValid && isDirty}
loading={isSubmitting}
>
{isSubmitting ? "Sending code..." : "Send unique code"}
</Button>
)}
</form>
</>
);
};