-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: filters in plane deploy implemented multi-level dropdown for plane deploy * style: spacing and fonts * feat: plane deploy implemented authentication/theming, created/modified all the required store & services * devL reactions, voting, comments and theme
- Loading branch information
1 parent
c65bbf8
commit 96399c7
Showing
97 changed files
with
6,580 additions
and
806 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
import React, { useEffect, useState, useCallback } from "react"; | ||
|
||
// react hook form | ||
import { useForm } from "react-hook-form"; | ||
|
||
// services | ||
import authenticationService from "services/authentication.service"; | ||
|
||
// hooks | ||
import useToast from "hooks/use-toast"; | ||
import useTimer from "hooks/use-timer"; | ||
|
||
// ui | ||
import { Input, PrimaryButton } from "components/ui"; | ||
|
||
// types | ||
type EmailCodeFormValues = { | ||
email: string; | ||
key?: string; | ||
token?: string; | ||
}; | ||
|
||
export const EmailCodeForm = ({ handleSignIn }: any) => { | ||
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 { setToastAlert } = useToast(); | ||
const { timer: resendCodeTimer, setTimer: setResendCodeTimer } = useTimer(); | ||
|
||
const { | ||
register, | ||
handleSubmit, | ||
setError, | ||
setValue, | ||
getValues, | ||
watch, | ||
formState: { errors, isSubmitting, isValid, isDirty }, | ||
} = useForm<EmailCodeFormValues>({ | ||
defaultValues: { | ||
email: "", | ||
key: "", | ||
token: "", | ||
}, | ||
mode: "onChange", | ||
reValidateMode: "onChange", | ||
}); | ||
|
||
const isResendDisabled = resendCodeTimer > 0 || isCodeResending || isSubmitting || errorResendingCode; | ||
|
||
const onSubmit = useCallback( | ||
async ({ email }: EmailCodeFormValues) => { | ||
setErrorResendingCode(false); | ||
await authenticationService | ||
.emailCode({ email }) | ||
.then((res) => { | ||
setValue("key", res.key); | ||
setCodeSent(true); | ||
}) | ||
.catch((err) => { | ||
setErrorResendingCode(true); | ||
setToastAlert({ | ||
title: "Oops!", | ||
type: "error", | ||
message: err?.error, | ||
}); | ||
}); | ||
}, | ||
[setToastAlert, setValue] | ||
); | ||
|
||
const handleSignin = async (formData: EmailCodeFormValues) => { | ||
setIsLoading(true); | ||
await authenticationService | ||
.magicSignIn(formData) | ||
.then((response) => { | ||
setIsLoading(false); | ||
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(() => { | ||
setErrorResendingCode(false); | ||
}, [emailOld]); | ||
|
||
useEffect(() => { | ||
const submitForm = (e: KeyboardEvent) => { | ||
if (!codeSent && e.key === "Enter") { | ||
e.preventDefault(); | ||
handleSubmit(onSubmit)().then(() => { | ||
setResendCodeTimer(30); | ||
}); | ||
} | ||
}; | ||
|
||
if (!codeSent) { | ||
window.addEventListener("keydown", submitForm); | ||
} | ||
|
||
return () => { | ||
window.removeEventListener("keydown", submitForm); | ||
}; | ||
}, [handleSubmit, codeSent, onSubmit, setResendCodeTimer]); | ||
|
||
return ( | ||
<> | ||
{(codeSent || codeResent) && ( | ||
<p className="text-center mt-4"> | ||
We have sent the sign in code. | ||
<br /> | ||
Please check your inbox at <span className="font-medium">{watch("email")}</span> | ||
</p> | ||
)} | ||
<form className="space-y-4 mt-10 sm:w-[360px] mx-auto"> | ||
<div className="space-y-1"> | ||
<Input | ||
id="email" | ||
type="email" | ||
placeholder="Enter your email address..." | ||
className="border-custom-border-300 h-[46px]" | ||
{...register("email", { | ||
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", | ||
})} | ||
/> | ||
{errors.email && <div className="text-sm text-red-500">{errors.email.message}</div>} | ||
</div> | ||
|
||
{codeSent && ( | ||
<> | ||
<Input | ||
id="token" | ||
type="token" | ||
{...register("token", { | ||
required: "Code is required", | ||
})} | ||
placeholder="Enter code..." | ||
className="border-custom-border-300 h-[46px]" | ||
/> | ||
{errors.token && <div className="text-sm text-red-500">{errors.token.message}</div>} | ||
<button | ||
type="button" | ||
className={`flex w-full justify-end text-xs outline-none ${ | ||
isResendDisabled ? "cursor-default text-custom-text-200" : "cursor-pointer text-custom-primary-100" | ||
} `} | ||
onClick={() => { | ||
setIsCodeResending(true); | ||
onSubmit({ email: getValues("email") }).then(() => { | ||
setCodeResent(true); | ||
setIsCodeResending(false); | ||
setResendCodeTimer(30); | ||
}); | ||
}} | ||
disabled={isResendDisabled} | ||
> | ||
{resendCodeTimer > 0 ? ( | ||
<span className="text-right">Request new code in {resendCodeTimer} seconds</span> | ||
) : isCodeResending ? ( | ||
"Sending new code..." | ||
) : errorResendingCode ? ( | ||
"Please try again later" | ||
) : ( | ||
<span className="font-medium">Resend code</span> | ||
)} | ||
</button> | ||
</> | ||
)} | ||
{codeSent ? ( | ||
<PrimaryButton | ||
type="submit" | ||
className="w-full text-center h-[46px]" | ||
size="md" | ||
onClick={handleSubmit(handleSignin)} | ||
disabled={!isValid && isDirty} | ||
loading={isLoading} | ||
> | ||
{isLoading ? "Signing in..." : "Sign in"} | ||
</PrimaryButton> | ||
) : ( | ||
<PrimaryButton | ||
className="w-full text-center h-[46px]" | ||
size="md" | ||
onClick={() => { | ||
handleSubmit(onSubmit)().then(() => { | ||
setResendCodeTimer(30); | ||
}); | ||
}} | ||
disabled={!isValid && isDirty} | ||
loading={isSubmitting} | ||
> | ||
{isSubmitting ? "Sending code..." : "Send sign in code"} | ||
</PrimaryButton> | ||
)} | ||
</form> | ||
</> | ||
); | ||
}; |
116 changes: 116 additions & 0 deletions
116
apps/space/app/(auth)/components/email-password-form.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
import React, { useState } from "react"; | ||
|
||
import { useRouter } from "next/router"; | ||
import Link from "next/link"; | ||
|
||
// react hook form | ||
import { useForm } from "react-hook-form"; | ||
// components | ||
import { EmailResetPasswordForm } from "./email-reset-password-form"; | ||
// ui | ||
import { Input, PrimaryButton } from "components/ui"; | ||
// types | ||
type EmailPasswordFormValues = { | ||
email: string; | ||
password?: string; | ||
medium?: string; | ||
}; | ||
|
||
type Props = { | ||
onSubmit: (formData: EmailPasswordFormValues) => Promise<void>; | ||
}; | ||
|
||
export const EmailPasswordForm: React.FC<Props> = ({ onSubmit }) => { | ||
const [isResettingPassword, setIsResettingPassword] = useState(false); | ||
|
||
const router = useRouter(); | ||
const isSignUpPage = router.pathname === "/sign-up"; | ||
|
||
const { | ||
register, | ||
handleSubmit, | ||
formState: { errors, isSubmitting, isValid, isDirty }, | ||
} = useForm<EmailPasswordFormValues>({ | ||
defaultValues: { | ||
email: "", | ||
password: "", | ||
medium: "email", | ||
}, | ||
mode: "onChange", | ||
reValidateMode: "onChange", | ||
}); | ||
|
||
return ( | ||
<> | ||
<h1 className="text-center text-2xl sm:text-2.5xl font-semibold text-custom-text-100"> | ||
{isResettingPassword ? "Reset your password" : isSignUpPage ? "Sign up on Plane" : "Sign in to Plane"} | ||
</h1> | ||
{isResettingPassword ? ( | ||
<EmailResetPasswordForm setIsResettingPassword={setIsResettingPassword} /> | ||
) : ( | ||
<form className="space-y-4 mt-10 w-full sm:w-[360px] mx-auto" onSubmit={handleSubmit(onSubmit)}> | ||
<div className="space-y-1"> | ||
<Input | ||
id="email" | ||
type="email" | ||
{...register("email", { | ||
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", | ||
})} | ||
placeholder="Enter your email address..." | ||
className="border-custom-border-300 h-[46px]" | ||
/> | ||
{errors.email && <div className="text-sm text-red-500">{errors.email.message}</div>} | ||
</div> | ||
<div className="space-y-1"> | ||
<Input | ||
id="password" | ||
type="password" | ||
{...register("password", { | ||
required: "Password is required", | ||
})} | ||
placeholder="Enter your password..." | ||
className="border-custom-border-300 h-[46px]" | ||
/> | ||
{errors.password && <div className="text-sm text-red-500">{errors.password.message}</div>} | ||
</div> | ||
<div className="text-right text-xs"> | ||
{isSignUpPage ? ( | ||
<Link href="/"> | ||
<a className="text-custom-text-200 hover:text-custom-primary-100">Already have an account? Sign in.</a> | ||
</Link> | ||
) : ( | ||
<button | ||
type="button" | ||
onClick={() => setIsResettingPassword(true)} | ||
className="text-custom-text-200 hover:text-custom-primary-100" | ||
> | ||
Forgot your password? | ||
</button> | ||
)} | ||
</div> | ||
<div> | ||
<PrimaryButton | ||
type="submit" | ||
className="w-full text-center h-[46px]" | ||
disabled={!isValid && isDirty} | ||
loading={isSubmitting} | ||
> | ||
{isSignUpPage ? (isSubmitting ? "Signing up..." : "Sign up") : isSubmitting ? "Signing in..." : "Sign in"} | ||
</PrimaryButton> | ||
{!isSignUpPage && ( | ||
<Link href="/sign-up"> | ||
<a className="block text-custom-text-200 hover:text-custom-primary-100 text-xs mt-4"> | ||
Don{"'"}t have an account? Sign up. | ||
</a> | ||
</Link> | ||
)} | ||
</div> | ||
</form> | ||
)} | ||
</> | ||
); | ||
}; |
Oops, something went wrong.