Skip to content

Commit

Permalink
feat: add auth validations, layouts and pages
Browse files Browse the repository at this point in the history
Signed-off-by: rajput-hemant <[email protected]>
  • Loading branch information
rajput-hemant committed Dec 10, 2023
1 parent dfa797d commit 579490b
Show file tree
Hide file tree
Showing 16 changed files with 938 additions and 40 deletions.
266 changes: 266 additions & 0 deletions app/(auth)/components/auth-form.tsx
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" />
)}
Google
</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>
);
}
28 changes: 28 additions & 0 deletions app/(auth)/components/auth-mode-toggle.tsx
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>
);
}
70 changes: 70 additions & 0 deletions app/(auth)/layout.tsx
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>
);
}
21 changes: 19 additions & 2 deletions app/(auth)/login/page.tsx
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>
);
}
Loading

0 comments on commit 579490b

Please sign in to comment.