generated from rajput-hemant/nextjs-template
-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add auth validations, layouts and pages
Signed-off-by: rajput-hemant <[email protected]>
- Loading branch information
1 parent
dfa797d
commit 579490b
Showing
16 changed files
with
938 additions
and
40 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,266 @@ | ||
"use client"; | ||
|
||
import React from "react"; | ||
import Link from "next/link"; | ||
import { zodResolver } from "@hookform/resolvers/zod"; | ||
import { Eye, EyeOff, Key, Loader2, Mail } from "lucide-react"; | ||
import { signIn } from "next-auth/react"; | ||
import { useForm } from "react-hook-form"; | ||
import type * as z from "zod"; | ||
|
||
import { cn } from "@/lib/utils"; | ||
import { authSchema } from "@/lib/validations"; | ||
import { toast } from "@/hooks/use-toast"; | ||
import { GitHub, Google } from "@/components/icons"; | ||
import { Button, buttonVariants } from "@/components/ui/button"; | ||
import { | ||
Form, | ||
FormControl, | ||
FormField, | ||
FormItem, | ||
FormLabel, | ||
FormMessage, | ||
} from "@/components/ui/form"; | ||
import { Input } from "@/components/ui/input"; | ||
import { | ||
Tooltip, | ||
TooltipContent, | ||
TooltipTrigger, | ||
} from "@/components/ui/tooltip"; | ||
|
||
type FormData = z.infer<typeof authSchema>; | ||
type AuthFormProps = { mode: "login" | "signup" | "reset" }; | ||
|
||
export function AuthForm({ mode }: AuthFormProps) { | ||
const [isSubmitting, setIsSubmitting] = React.useState(false); | ||
const [isPassVisible, setIsPassVisible] = React.useState(false); | ||
const [isConfirmPassVisible, setIsConfirmPassVisible] = React.useState(false); | ||
|
||
const form = useForm<FormData>({ resolver: zodResolver(authSchema) }); | ||
|
||
function onSubmit(values: FormData) { | ||
console.log(values); | ||
} | ||
|
||
async function googleSignInHandler() { | ||
setIsSubmitting(true); | ||
|
||
try { | ||
toast({ | ||
title: "Signing in...", | ||
description: "Please wait while we sign you in", | ||
}); | ||
await signIn("google"); | ||
} catch (error) { | ||
// ... | ||
} | ||
|
||
setIsSubmitting(false); | ||
} | ||
|
||
async function githubSignInHandler() { | ||
setIsSubmitting(true); | ||
|
||
try { | ||
toast({ | ||
title: "Signing in...", | ||
description: "Please wait while we sign you in", | ||
}); | ||
await signIn("github"); | ||
} catch (error) { | ||
// ... | ||
} | ||
|
||
setIsSubmitting(false); | ||
} | ||
|
||
const togglePassVisibility = () => setIsPassVisible(!isPassVisible); | ||
const toggleConfirmPassVisibility = () => | ||
setIsConfirmPassVisible(!isConfirmPassVisible); | ||
|
||
return ( | ||
<Form {...form}> | ||
<Link | ||
href={mode === "login" ? "/signup" : "/login"} | ||
className={cn( | ||
buttonVariants({ variant: "secondary" }), | ||
"absolute right-4 top-4 md:right-8 md:top-8" | ||
)} | ||
> | ||
{mode === "login" ? "Sign Up" : "Login"} | ||
</Link> | ||
|
||
<form onSubmit={form.handleSubmit(onSubmit)} className="mt-4 space-y-2"> | ||
<FormField | ||
name="email" | ||
control={form.control} | ||
render={({ field }) => ( | ||
<FormItem className="space-y-1"> | ||
<FormLabel className="sr-only">Email</FormLabel> | ||
<FormControl> | ||
<Input | ||
type="email" | ||
disabled={isSubmitting} | ||
placeholder="[email protected]" | ||
className="shadow-sm" | ||
{...field} | ||
/> | ||
</FormControl> | ||
<FormMessage /> | ||
</FormItem> | ||
)} | ||
/> | ||
<FormField | ||
name="password" | ||
control={form.control} | ||
render={({ field }) => ( | ||
<FormItem className="space-y-1"> | ||
<FormLabel className="sr-only">Password</FormLabel> | ||
<FormControl> | ||
<div className="relative"> | ||
<Input | ||
type={isPassVisible ? "text" : "password"} | ||
disabled={isSubmitting} | ||
placeholder="••••••••••" | ||
className="pr-8 shadow-sm" | ||
{...field} | ||
/> | ||
<Tooltip delayDuration={150}> | ||
<TooltipTrigger | ||
type="button" | ||
disabled={!field.value} | ||
onClick={togglePassVisibility} | ||
className="absolute inset-y-0 right-2 my-auto text-muted-foreground hover:text-foreground disabled:pointer-events-none disabled:opacity-50" | ||
> | ||
{isPassVisible ? ( | ||
<EyeOff className="h-5 w-5" /> | ||
) : ( | ||
<Eye className="h-5 w-5" /> | ||
)} | ||
</TooltipTrigger> | ||
|
||
<TooltipContent> | ||
<p className="text-xs"> | ||
{isPassVisible ? "Hide Password" : "Show Password"} | ||
</p> | ||
</TooltipContent> | ||
</Tooltip> | ||
</div> | ||
</FormControl> | ||
<FormMessage /> | ||
</FormItem> | ||
)} | ||
/> | ||
|
||
{mode !== "login" && ( | ||
<FormField | ||
name="confirmPassword" | ||
control={form.control} | ||
render={({ field }) => ( | ||
<FormItem className="space-y-1"> | ||
<FormLabel className="sr-only">Confirm Password</FormLabel> | ||
<FormControl> | ||
<div className="relative"> | ||
<Input | ||
type={isConfirmPassVisible ? "text" : "password"} | ||
disabled={isSubmitting} | ||
placeholder="••••••••••" | ||
className="pr-8 shadow-sm" | ||
{...field} | ||
/> | ||
<Tooltip delayDuration={150}> | ||
<TooltipTrigger | ||
type="button" | ||
disabled={!field.value} | ||
onClick={toggleConfirmPassVisibility} | ||
className="absolute inset-y-0 right-2 my-auto text-muted-foreground hover:text-foreground disabled:pointer-events-none disabled:opacity-50" | ||
> | ||
{isConfirmPassVisible ? ( | ||
<EyeOff className="h-5 w-5" /> | ||
) : ( | ||
<Eye className="h-5 w-5" /> | ||
)} | ||
</TooltipTrigger> | ||
|
||
<TooltipContent> | ||
<p className="text-xs"> | ||
{isConfirmPassVisible | ||
? "Hide Password" | ||
: "Show Password"} | ||
</p> | ||
</TooltipContent> | ||
</Tooltip> | ||
</div> | ||
</FormControl> | ||
<FormMessage /> | ||
</FormItem> | ||
)} | ||
/> | ||
)} | ||
|
||
<Button | ||
type="submit" | ||
disabled={isSubmitting} | ||
className="w-full shadow-md" | ||
> | ||
{isSubmitting ? ( | ||
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> | ||
) : ( | ||
<> | ||
{mode === "reset" ? ( | ||
<Key className="mr-2 h-4 w-4" /> | ||
) : ( | ||
<Mail className="mr-2 h-4 w-4" /> | ||
)} | ||
</> | ||
)} | ||
{mode === "reset" && "Reset Password"} | ||
{mode === "login" && "Login with Email"} | ||
{mode === "signup" && "Sign Up with Email"} | ||
</Button> | ||
</form> | ||
|
||
{mode === "login" && ( | ||
<p className="mt-2 text-xs text-muted-foreground underline-offset-4 hover:text-foreground hover:underline"> | ||
<Link href="/reset-password">Forgot password?</Link> | ||
</p> | ||
)} | ||
|
||
<div className="relative my-4"> | ||
<span className="absolute inset-x-0 inset-y-1/2 border-t" /> | ||
|
||
<span className="relative mx-auto flex w-fit bg-background px-2 text-xs uppercase text-muted-foreground transition-colors duration-0"> | ||
Or continue with | ||
</span> | ||
</div> | ||
|
||
<div className="mt-6 flex w-full flex-col space-y-2 text-white"> | ||
<Button | ||
onClick={googleSignInHandler} | ||
disabled={isSubmitting} | ||
className="w-full shadow-md" | ||
> | ||
{isSubmitting ? ( | ||
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> | ||
) : ( | ||
<Google className="mr-2 h-4 w-4" /> | ||
)} | ||
</Button> | ||
|
||
<Button | ||
onClick={githubSignInHandler} | ||
disabled={isSubmitting} | ||
className="w-full shadow-md" | ||
> | ||
{isSubmitting ? ( | ||
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> | ||
) : ( | ||
<GitHub className="mr-2 h-4 w-4" /> | ||
)} | ||
GitHub | ||
</Button> | ||
</div> | ||
</Form> | ||
); | ||
} |
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,28 @@ | ||
"use client"; | ||
|
||
import Link from "next/link"; | ||
import { usePathname } from "next/navigation"; | ||
|
||
import { cn } from "@/lib/utils"; | ||
import { buttonVariants } from "@/components/ui/button"; | ||
|
||
export function AuthModeToggle() { | ||
const isLoginPage = usePathname() === "/login"; | ||
|
||
return ( | ||
<div className="absolute right-4 top-4 md:right-8 md:top-8"> | ||
<span className="hidden text-sm text-muted-foreground md:inline-block"> | ||
{isLoginPage | ||
? "Don't have an account yet?" | ||
: "Already have an account?"} | ||
</span> | ||
|
||
<Link | ||
href={isLoginPage ? "/signup" : "/login"} | ||
className={cn(buttonVariants({ variant: "outline" }), "ml-2")} | ||
> | ||
{isLoginPage ? "Sign up" : "Login"} | ||
</Link> | ||
</div> | ||
); | ||
} |
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,70 @@ | ||
import React from "react"; | ||
import Image from "next/image"; | ||
import Link from "next/link"; | ||
|
||
import { siteConfig } from "@/config/site"; | ||
import { Logo } from "@/components/icons"; | ||
|
||
export default function AuthLayout({ children }: React.PropsWithChildren) { | ||
return ( | ||
<div className="container h-screen flex-col items-center justify-center md:grid lg:max-w-none lg:grid-cols-2 lg:px-0"> | ||
<div className="relative hidden h-full flex-col justify-between border-r p-10 lg:flex"> | ||
<div className="absolute inset-0 bg-zinc-900" /> | ||
|
||
<div className="z-10 flex items-center gap-2 font-handwriting text-2xl font-medium lowercase text-background dark:text-foreground"> | ||
<Logo size={32} /> | ||
{siteConfig.name} | ||
</div> | ||
|
||
<div className="m-auto"> | ||
<Image | ||
src="/illustrations/success.svg" | ||
alt="Get started" | ||
width={500} | ||
height={500} | ||
className="drop-shadow-xl invert" | ||
/> | ||
</div> | ||
|
||
<div className="z-10 ml-auto text-muted-foreground"> | ||
<p className="text-sm"> | ||
Illustrations by{" "} | ||
<a | ||
href="https://popsy.co/" | ||
className="underline underline-offset-4 transition-colors hover:text-background dark:hover:text-foreground" | ||
> | ||
Popsy | ||
</a> | ||
. | ||
</p> | ||
</div> | ||
</div> | ||
|
||
<div className="relative my-auto flex h-full lg:p-8"> | ||
<div className="z-10 mx-auto flex w-full flex-col justify-center space-y-6 sm:w-[550px]"> | ||
<Logo size={56} className="mx-auto" /> | ||
|
||
{children} | ||
|
||
<p className="mx-auto w-[350px] px-8 text-center text-sm text-muted-foreground"> | ||
By clicking continue, you agree to our{" "} | ||
<Link | ||
href="/terms" | ||
className="underline underline-offset-4 hover:text-primary" | ||
> | ||
Terms of Service | ||
</Link>{" "} | ||
and{" "} | ||
<Link | ||
href="/privacy" | ||
className="underline underline-offset-4 hover:text-primary" | ||
> | ||
Privacy Policy | ||
</Link> | ||
. | ||
</p> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
} |
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 |
---|---|---|
@@ -1,7 +1,24 @@ | ||
import { AuthForm } from "../components/auth-form"; | ||
|
||
export const metadata = { | ||
title: "Login", | ||
description: "Login to access your account", | ||
}; | ||
|
||
export default function LoginPage() { | ||
return ( | ||
<div> | ||
<h1>Login Page</h1> | ||
<div className="flex flex-col space-y-2 text-center"> | ||
<h1 className="font-heading text-3xl drop-shadow-xl dark:bg-gradient-to-br dark:from-foreground dark:to-gray-500 dark:bg-clip-text dark:text-transparent sm:text-4xl md:text-5xl"> | ||
Login | ||
</h1> | ||
|
||
<p className="text-sm text-muted-foreground"> | ||
Enter your email below to login | ||
</p> | ||
|
||
<div className="px-8 sm:mx-auto sm:w-[350px] sm:px-0"> | ||
<AuthForm mode="login" /> | ||
</div> | ||
</div> | ||
); | ||
} |
Oops, something went wrong.