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!" : "Let’s 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> </> ); };