-
Yusuf Akgül authoredYusuf Akgül authored
user-auth-form.tsx 9.75 KiB
'use client'
import { zodResolver } from "@hookform/resolvers/zod"
import { signIn } from 'next-auth/react'
import { useRouter, useSearchParams } from "next/navigation"
import { HTMLAttributes, useState } from 'react'
import { useForm } from 'react-hook-form'
import * as z from "zod"
import { Icons } from '@/components/icons'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { ToastAction } from "@/components/ui/toast"
import { toast } from "@/components/ui/use-toast"
import { cn } from '@/lib/utils'
import { userAuthSchema } from "@/lib/validations/auth"
interface UserAuthFormProps extends HTMLAttributes<HTMLDivElement> {
type: "login" | "signup"
}
type FormData = z.infer<typeof userAuthSchema>
export function UserAuthForm({ type, className, ...props }: UserAuthFormProps) {
const {
register,
handleSubmit,
setError,
formState: { errors },
} = useForm<FormData>({
resolver: zodResolver(userAuthSchema),
})
const [isLoading, setIsLoading] = useState<boolean>(false)
const [isGitHubLoading, setIsGitHubLoading] = useState<boolean>(false)
const router = useRouter();
const searchParams = useSearchParams()
async function onSubmit(data: FormData) {
setIsLoading(true)
if (type === "signup") {
const res = await fetch('/api/signup', {
method: 'POST',
body: JSON.stringify({
username: data.username,
email: data.email,
password: data.password
}),
headers: {
'Content-Type': 'application/json'
}
})
if (!res.ok) {
if (res.status === 422) {
setError('email', { type: 'manual', message: 'This email is already in use. Please choose another one.' });
}
setIsLoading(false)
return toast({
variant: "destructive",
title: "Uh oh! Something went wrong.",
description: "Your sign up request failed. Please try again.",
})
}
}
const signInResult = await signIn("credentials", {
usernameOrEmail: data.email?.toLowerCase() || data.usernameOrEmail?.toLowerCase(),
password: data.password,
redirect: false,
callbackUrl: searchParams?.get("from") || "/home",
});
setIsLoading(false)
if (signInResult?.error) {
if (signInResult.error === "user not found") {
setError('usernameOrEmail', {
type: 'manual',
message: 'Sorry, we couldn\'t find an account with the provided email / username. Please double-check your input or create a new account.'
});
}
if (signInResult.error === "invalid password") {
setError('password', {
type: 'manual',
message: 'Sorry, but it seems like the password you entered is invalid. Please try again.'
});
}
return toast({
variant: "destructive",
title: "Uh oh! Something went wrong.",
description: "Your log in request failed. Please try again.",
action: <ToastAction altText="Try again">Try again</ToastAction>,
})
}
router.push("/home")
if (type === "signup") {
return toast({
title: "Congratulations!",
description: "Your account has been created. You will be redirected shortly.",
})
} else {
return toast({
title: "Login successful.",
description: "You will be redirected shortly.",
})
}
}
async function onGitHub() {
setIsGitHubLoading(true)
await signIn("github", { callbackUrl: searchParams?.get("from") || "/home" })
}
return (
<div className={cn("grid gap-6", className)} {...props}>
<form onSubmit={handleSubmit(onSubmit)}>
<div className="grid gap-2">
{type === "login" ?
<div className="grid gap-1">
<Label className="sr-only" htmlFor="usernameOrEmail">
Username or email
</Label>
<Input
id="usernameOrEmail"
placeholder="Your username or email"
type="text"
autoCapitalize="none"
autoComplete="username email"
autoCorrect="off"
disabled={isLoading || isGitHubLoading}
{...register("usernameOrEmail", { required: true })}
/>
{errors?.usernameOrEmail && (
<p className="px-1 text-xs text-red-600">
{errors.usernameOrEmail.message}
</p>
)}
</div> : null}
{type === "signup" ?
<>
<div className="grid gap-1">
<Label className="sr-only" htmlFor="username">
Username
</Label>
<Input
id="username"
placeholder="Your username"
type="username"
autoCapitalize="none"
autoComplete="username"
autoCorrect="off"
disabled={isLoading || isGitHubLoading}
{...register("username", { required: true })}
/>
{errors?.username && (
<p className="px-1 text-xs text-red-600">
{errors.username.message}
</p>
)}
</div>
<div className="grid gap-1">
<Label className="sr-only" htmlFor="email">
Email
</Label>
<Input
id="email"
placeholder="Your email"
type="email"
autoCapitalize="none"
autoComplete="email"
autoCorrect="off"
disabled={isLoading || isGitHubLoading}
{...register("email", { required: true })}
/>
{errors?.email && (
<p className="px-1 text-xs text-red-600">
{errors.email.message}
</p>
)}
</div>
</> : null}
<div className="grid gap-1">
<Label className="sr-only" htmlFor="password">
Password
</Label>
<Input
id="password"
placeholder="Your password"
type="password"
autoCapitalize="none"
autoComplete="new-password"
autoCorrect="off"
disabled={isLoading || isGitHubLoading}
{...register("password", { required: true })}
/>
{errors?.password && (
<p className="px-1 text-xs text-red-600">
{errors.password.message}
</p>
)}
</div>
<Button disabled={isLoading} type="submit">
{isLoading && (
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
)}
{type === "signup" ? "Sign Up" : "Log In"}
</Button>
</div>
</form>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<span className="w-full border-t" />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background px-2 text-muted-foreground">
Or continue with
</span>
</div>
</div>
<Button
variant="outline"
type="button"
onClick={onGitHub}
disabled={isLoading || isGitHubLoading}
>
{isGitHubLoading ? (
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
) : (
<Icons.github className="mr-2 h-4 w-4" />
)}{" "}
Github
</Button>
</div>
)
}