Skip to content
Snippets Groups Projects
Commit fe53d5fd authored by Yusuf Akgül's avatar Yusuf Akgül :hatching_chick:
Browse files

auth fixed + toaster

parent d032c0c7
No related branches found
No related tags found
1 merge request!19Feat.auth fixes
...@@ -3,21 +3,23 @@ import { hash } from 'bcrypt' ...@@ -3,21 +3,23 @@ import { hash } from 'bcrypt'
import { NextResponse } from 'next/server' import { NextResponse } from 'next/server'
export async function POST(req: Request) { export async function POST(req: Request) {
console.log("aaaa", req)
try { try {
const { username, email, password } = await req.json() const { username, email, password } = await req.json()
const hashed = await hash(password, 12) const hashed = await hash(password, 12)
const user = await db.user.create({ const user = await db.user.create({
data: { data: {
username, name: username,
email, username: username.toLowerCase(),
email: email.toLowerCase(),
password: hashed password: hashed
} }
}) })
return NextResponse.json({ return NextResponse.json({
username: user.username, usernameOrEmail: user.email
email: user.email
}) })
} catch (err: any) { } catch (err: any) {
return new NextResponse(JSON.stringify({ return new NextResponse(JSON.stringify({
...@@ -25,4 +27,19 @@ export async function POST(req: Request) { ...@@ -25,4 +27,19 @@ export async function POST(req: Request) {
}), { status: 500 } }), { status: 500 }
) )
} }
} }
\ No newline at end of file
// let isUnique = false;
// while (!isUnique) {
// const existingUserName = await db.user.findUnique({
// where: {
// username: credentials.username
// }
// })
// if (existingUserName) {
// credentials.username = `${credentials.username}${Math.floor(Math.random() * 1000)}`
// } else {
// isUnique = true;
// }
// }
\ No newline at end of file
...@@ -4,6 +4,7 @@ import './globals.css' ...@@ -4,6 +4,7 @@ import './globals.css'
import Providers from '@/components/react-query/provider' import Providers from '@/components/react-query/provider'
import SiteLoad from '@/components/site-loading' import SiteLoad from '@/components/site-loading'
import { ThemeProvider } from '@/components/ui/theme-provider' import { ThemeProvider } from '@/components/ui/theme-provider'
import { Toaster } from '@/components/ui/toaster'
import { Suspense } from 'react' import { Suspense } from 'react'
const inter = Inter({ subsets: ['latin'] }) const inter = Inter({ subsets: ['latin'] })
...@@ -26,6 +27,7 @@ export default function RootLayout({ ...@@ -26,6 +27,7 @@ export default function RootLayout({
<Suspense fallback={<SiteLoad />}> <Suspense fallback={<SiteLoad />}>
<Providers> <Providers>
{children} {children}
<Toaster />
</Providers> </Providers>
</Suspense> </Suspense>
</ThemeProvider> </ThemeProvider>
......
import * as React from "react" import * as React from "react"
import * as ToastPrimitives from "@radix-ui/react-toast" import * as ToastPrimitives from "@radix-ui/react-toast"
import { VariantProps, cva } from "class-variance-authority" import { cva, type VariantProps } from "class-variance-authority"
import { X } from "lucide-react" import { X } from "lucide-react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
......
"use client"
import {
Toast,
ToastClose,
ToastDescription,
ToastProvider,
ToastTitle,
ToastViewport,
} from "@/components/ui/toast"
import { useToast } from "@/components/ui/use-toast"
export function Toaster() {
const { toasts } = useToast()
return (
<ToastProvider>
{toasts.map(function ({ id, title, description, action, ...props }) {
return (
<Toast key={id} {...props}>
<div className="grid gap-1">
{title && <ToastTitle>{title}</ToastTitle>}
{description && (
<ToastDescription>{description}</ToastDescription>
)}
</div>
{action}
<ToastClose />
</Toast>
)
})}
<ToastViewport />
</ToastProvider>
)
}
\ No newline at end of file
// Inspired by react-hot-toast library // Inspired by react-hot-toast library
import * as React from "react" import * as React from "react"
import { ToastActionElement, type ToastProps } from "@/components/ui/toast" import type { ToastActionElement, ToastProps } from "@/components/ui/toast"
const TOAST_LIMIT = 1 const TOAST_LIMIT = 1
const TOAST_REMOVE_DELAY = 1000000 const TOAST_REMOVE_DELAY = 1000000
...@@ -135,7 +135,7 @@ function dispatch(action: Action) { ...@@ -135,7 +135,7 @@ function dispatch(action: Action) {
}) })
} }
interface Toast extends Omit<ToasterToast, "id"> {} type Toast = Omit<ToasterToast, "id">
function toast({ ...props }: Toast) { function toast({ ...props }: Toast) {
const id = genId() const id = genId()
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
import { zodResolver } from "@hookform/resolvers/zod" import { zodResolver } from "@hookform/resolvers/zod"
import { signIn } from 'next-auth/react' import { signIn } from 'next-auth/react'
import { useSearchParams } from 'next/navigation'
import { HTMLAttributes, useState } from 'react' import { HTMLAttributes, useState } from 'react'
import { useForm } from 'react-hook-form' import { useForm } from 'react-hook-form'
import * as z from "zod" import * as z from "zod"
...@@ -11,10 +10,10 @@ import { Icons } from '@/components/icons' ...@@ -11,10 +10,10 @@ import { Icons } from '@/components/icons'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input' import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label' import { Label } from '@/components/ui/label'
import { useToast } from '@/components/ui/use-toast' import { ToastAction } from "@/components/ui/toast"
import { toast } from "@/components/ui/use-toast"
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { userAuthSchema } from '@/lib/validations/auth' import { userAuthSchema } from "@/lib/validations/auth"
import { ToastAction } from "./ui/toast"
interface UserAuthFormProps extends HTMLAttributes<HTMLDivElement> { interface UserAuthFormProps extends HTMLAttributes<HTMLDivElement> {
type: "login" | "signup" type: "login" | "signup"
...@@ -32,9 +31,6 @@ export function UserAuthForm({ type, className, ...props }: UserAuthFormProps) { ...@@ -32,9 +31,6 @@ export function UserAuthForm({ type, className, ...props }: UserAuthFormProps) {
}) })
const [isLoading, setIsLoading] = useState<boolean>(false) const [isLoading, setIsLoading] = useState<boolean>(false)
const [isGitHubLoading, setIsGitHubLoading] = useState<boolean>(false) const [isGitHubLoading, setIsGitHubLoading] = useState<boolean>(false)
const searchParams = useSearchParams()
const { toast } = useToast()
async function onSubmit(data: FormData) { async function onSubmit(data: FormData) {
setIsLoading(true) setIsLoading(true)
...@@ -61,19 +57,17 @@ export function UserAuthForm({ type, className, ...props }: UserAuthFormProps) { ...@@ -61,19 +57,17 @@ export function UserAuthForm({ type, className, ...props }: UserAuthFormProps) {
}) })
} }
} }
const signInResult = await signIn("credentials", { const signInResult = await signIn("credentials", {
username: data.username, usernameOrEmail: data.email?.toLowerCase() || data.usernameOrEmail?.toLowerCase(),
email: data.email,
password: data.password, password: data.password,
redirect: false, redirect: true,
callbackUrl: "/home", callbackUrl: "/home",
}); });
setIsLoading(false) setIsLoading(false)
if (!signInResult?.ok) { if (signInResult?.error) {
toast({ return toast({
variant: "destructive", variant: "destructive",
title: "Uh oh! Something went wrong.", title: "Uh oh! Something went wrong.",
description: "Your log in request failed. Please try again.", description: "Your log in request failed. Please try again.",
...@@ -81,113 +75,142 @@ export function UserAuthForm({ type, className, ...props }: UserAuthFormProps) { ...@@ -81,113 +75,142 @@ export function UserAuthForm({ type, className, ...props }: UserAuthFormProps) {
}) })
} }
// toast({ if (type === "signup") {
// title: "Check your email.", return toast({
// description: "We sent you a login link. Be sure to check your spam too.", title: "Congratulations!",
// }) description: "Your account has been created. You will be redirected shortly.",
})
} else {
return toast({
title: "Logging in.",
description: "You will be redirected shortly.",
})
}
} }
return ( return (
<> <div className={cn("grid gap-6", className)} {...props}>
<div className={cn("grid gap-6", className)} {...props}> <form onSubmit={handleSubmit(onSubmit)}>
<form onSubmit={handleSubmit(onSubmit)}> <div className="grid gap-2">
<div className="grid gap-2"> {type === "login" ?
<div className="grid gap-1"> <div className="grid gap-1">
<Label className="sr-only" htmlFor="username"> <Label className="sr-only" htmlFor="usernameOrEmail">
Username Username or email
</Label>
<Input
id="username"
placeholder="Your username"
type="username"
autoCapitalize="none"
autoComplete="username"
autoCorrect="off"
disabled={isLoading || isGitHubLoading}
{...register("username")}
/>
{errors?.username && (
<p className="px-1 text-xs text-red-600">
{errors.username.message}
</p>
)}
</div>
{type === "signup" ? <div className="grid gap-1">
<Label className="sr-only" htmlFor="email">
Email
</Label> </Label>
<Input <Input
id="email" id="usernameOrEmail"
placeholder="Your email" placeholder="Your username or email"
type="email" type="text"
autoCapitalize="none" autoCapitalize="none"
autoComplete="email" autoComplete="username email"
autoCorrect="off" autoCorrect="off"
disabled={isLoading || isGitHubLoading} disabled={isLoading || isGitHubLoading}
{...register("email")} {...register("usernameOrEmail", { required: true })}
/> />
{errors?.email && ( {errors?.usernameOrEmail && (
<p className="px-1 text-xs text-red-600"> <p className="px-1 text-xs text-red-600">
{errors.email.message} {errors.usernameOrEmail.message}
</p> </p>
)} )}
</div> : null} </div> : null}
<div className="grid gap-1"> {type === "signup" ?
<Label className="sr-only" htmlFor="password"> <>
Password <div className="grid gap-1">
</Label> <Label className="sr-only" htmlFor="username">
<Input Username
id="password" </Label>
placeholder="Your password" <Input
type="password" id="username"
autoCapitalize="none" placeholder="Your username"
autoComplete="new-password" type="username"
autoCorrect="off" autoCapitalize="none"
disabled={isLoading || isGitHubLoading} autoComplete="username"
{...register("password")} autoCorrect="off"
/> disabled={isLoading || isGitHubLoading}
{errors?.password && ( {...register("username", { required: true })}
<p className="px-1 text-xs text-red-600"> />
{errors.password.message} {errors?.username && (
</p> <p className="px-1 text-xs text-red-600">
)} {errors.username.message}
</div> </p>
<Button disabled={isLoading}> )}
{isLoading && ( </div>
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" /> <div className="grid gap-1">
)} <Label className="sr-only" htmlFor="email">
{type === "signup" ? "Sign Up" : "Log In"} Email
</Button> </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> </div>
</form> <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="relative">
<div className="absolute inset-0 flex items-center"> <div className="absolute inset-0 flex items-center">
<span className="w-full border-t" /> <span className="w-full border-t" />
</div> </div>
<div className="relative flex justify-center text-xs uppercase"> <div className="relative flex justify-center text-xs uppercase">
<span className="bg-background px-2 text-muted-foreground"> <span className="bg-background px-2 text-muted-foreground">
Or continue with Or continue with
</span> </span>
</div>
</div> </div>
<Button
variant="outline"
type="button"
onClick={() => {
setIsGitHubLoading(true)
signIn("github")
}}
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> </div>
</> <Button
variant="outline"
type="button"
onClick={() => {
setIsGitHubLoading(true)
signIn("github")
}}
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>
) )
} }
\ No newline at end of file
...@@ -17,42 +17,29 @@ export const authOptions: NextAuthOptions = { ...@@ -17,42 +17,29 @@ export const authOptions: NextAuthOptions = {
}, },
providers: [ providers: [
GitHubProvider({ GitHubProvider({
clientId: env.GITHUB_CLIENT_ID as string, clientId: env.GITHUB_CLIENT_ID,
clientSecret: env.GITHUB_CLIENT_SECRET as string, clientSecret: env.GITHUB_CLIENT_SECRET,
}), }),
CredentialsProvider({ CredentialsProvider({
name: 'Login', name: 'Login',
credentials: { credentials: {
username: { label: 'Username', type: 'text' }, usernameOrEmail: { label: 'Username or Email', type: 'text' },
email: { label: 'Email', type: 'email', placeholder: 'hello@example.com' },
password: { label: 'Password', type: 'password' } password: { label: 'Password', type: 'password' }
}, },
async authorize(credentials) { async authorize(credentials) {
if (!credentials?.username || !credentials.email || !credentials.password) { if (!credentials?.usernameOrEmail || !credentials?.password) {
return null return null
} }
let isUnique = false; const user = await db.user.findFirst({
while (!isUnique) {
const existingUserName = await db.user.findUnique({
where: {
username: credentials.username
}
})
if (existingUserName) {
credentials.username = `${credentials.username}${Math.floor(Math.random() * 1000)}`
} else {
isUnique = true;
}
}
const user = await db.user.findUnique({
where: { where: {
email: credentials.email OR: [
} { username: credentials.usernameOrEmail.toLowerCase() },
}) { email: credentials.usernameOrEmail.toLowerCase() },
],
},
});
if (!user) { if (!user) {
return null return null
...@@ -71,7 +58,6 @@ export const authOptions: NextAuthOptions = { ...@@ -71,7 +58,6 @@ export const authOptions: NextAuthOptions = {
id: user.id, id: user.id,
username: user.username, username: user.username,
email: user.email, email: user.email,
name: user.name,
} }
} }
}) })
......
import * as z from "zod" import * as z from "zod";
export const userAuthSchema = z.object({ export const userAuthSchema = z.object({
username: z.string().min(3).max(15), usernameOrEmail: z
email: z.string().email(), .union([
password: z.string().min(6).max(18), z.string().min(3, "Username or email must be at least 3 characters").max(15, "Username or email must be at most 15 characters"),
}) z.string().email("Invalid email format"),
\ No newline at end of file ])
.optional(),
username: z.string().min(3, "Username must be at least 3 characters").max(15, "Username must be at most 15 characters").optional(),
email: z.string().email("Invalid email format").optional(),
password: z.string().min(6, "Password must be at least 6 characters").max(18, "Password must be at most 18 characters"),
});
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment