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'
import { NextResponse } from 'next/server'
export async function POST(req: Request) {
console.log("aaaa", req)
try {
const { username, email, password } = await req.json()
const hashed = await hash(password, 12)
const user = await db.user.create({
data: {
username,
email,
name: username,
username: username.toLowerCase(),
email: email.toLowerCase(),
password: hashed
}
})
return NextResponse.json({
username: user.username,
email: user.email
usernameOrEmail: user.email
})
} catch (err: any) {
return new NextResponse(JSON.stringify({
......@@ -25,4 +27,19 @@ export async function POST(req: Request) {
}), { 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'
import Providers from '@/components/react-query/provider'
import SiteLoad from '@/components/site-loading'
import { ThemeProvider } from '@/components/ui/theme-provider'
import { Toaster } from '@/components/ui/toaster'
import { Suspense } from 'react'
const inter = Inter({ subsets: ['latin'] })
......@@ -26,6 +27,7 @@ export default function RootLayout({
<Suspense fallback={<SiteLoad />}>
<Providers>
{children}
<Toaster />
</Providers>
</Suspense>
</ThemeProvider>
......
import * as React from "react"
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 { 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
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_REMOVE_DELAY = 1000000
......@@ -135,7 +135,7 @@ function dispatch(action: Action) {
})
}
interface Toast extends Omit<ToasterToast, "id"> {}
type Toast = Omit<ToasterToast, "id">
function toast({ ...props }: Toast) {
const id = genId()
......
......@@ -2,7 +2,6 @@
import { zodResolver } from "@hookform/resolvers/zod"
import { signIn } from 'next-auth/react'
import { useSearchParams } from 'next/navigation'
import { HTMLAttributes, useState } from 'react'
import { useForm } from 'react-hook-form'
import * as z from "zod"
......@@ -11,10 +10,10 @@ import { Icons } from '@/components/icons'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
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 { userAuthSchema } from '@/lib/validations/auth'
import { ToastAction } from "./ui/toast"
import { userAuthSchema } from "@/lib/validations/auth"
interface UserAuthFormProps extends HTMLAttributes<HTMLDivElement> {
type: "login" | "signup"
......@@ -32,9 +31,6 @@ export function UserAuthForm({ type, className, ...props }: UserAuthFormProps) {
})
const [isLoading, setIsLoading] = useState<boolean>(false)
const [isGitHubLoading, setIsGitHubLoading] = useState<boolean>(false)
const searchParams = useSearchParams()
const { toast } = useToast()
async function onSubmit(data: FormData) {
setIsLoading(true)
......@@ -61,19 +57,17 @@ export function UserAuthForm({ type, className, ...props }: UserAuthFormProps) {
})
}
}
const signInResult = await signIn("credentials", {
username: data.username,
email: data.email,
usernameOrEmail: data.email?.toLowerCase() || data.usernameOrEmail?.toLowerCase(),
password: data.password,
redirect: false,
redirect: true,
callbackUrl: "/home",
});
setIsLoading(false)
if (!signInResult?.ok) {
toast({
if (signInResult?.error) {
return toast({
variant: "destructive",
title: "Uh oh! Something went wrong.",
description: "Your log in request failed. Please try again.",
......@@ -81,113 +75,142 @@ export function UserAuthForm({ type, className, ...props }: UserAuthFormProps) {
})
}
// toast({
// title: "Check your email.",
// description: "We sent you a login link. Be sure to check your spam too.",
// })
if (type === "signup") {
return toast({
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 (
<>
<div className={cn("grid gap-6", className)} {...props}>
<form onSubmit={handleSubmit(onSubmit)}>
<div className="grid gap-2">
<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="username">
Username
</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 className="sr-only" htmlFor="usernameOrEmail">
Username or email
</Label>
<Input
id="email"
placeholder="Your email"
type="email"
id="usernameOrEmail"
placeholder="Your username or email"
type="text"
autoCapitalize="none"
autoComplete="email"
autoComplete="username email"
autoCorrect="off"
disabled={isLoading || isGitHubLoading}
{...register("email")}
{...register("usernameOrEmail", { required: true })}
/>
{errors?.email && (
{errors?.usernameOrEmail && (
<p className="px-1 text-xs text-red-600">
{errors.email.message}
{errors.usernameOrEmail.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")}
/>
{errors?.password && (
<p className="px-1 text-xs text-red-600">
{errors.password.message}
</p>
)}
</div>
<Button disabled={isLoading}>
{isLoading && (
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
)}
{type === "signup" ? "Sign Up" : "Log In"}
</Button>
{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>
</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="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 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>
<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>
</>
<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 = {
},
providers: [
GitHubProvider({
clientId: env.GITHUB_CLIENT_ID as string,
clientSecret: env.GITHUB_CLIENT_SECRET as string,
clientId: env.GITHUB_CLIENT_ID,
clientSecret: env.GITHUB_CLIENT_SECRET,
}),
CredentialsProvider({
name: 'Login',
credentials: {
username: { label: 'Username', type: 'text' },
email: { label: 'Email', type: 'email', placeholder: 'hello@example.com' },
usernameOrEmail: { label: 'Username or Email', type: 'text' },
password: { label: 'Password', type: 'password' }
},
async authorize(credentials) {
if (!credentials?.username || !credentials.email || !credentials.password) {
if (!credentials?.usernameOrEmail || !credentials?.password) {
return null
}
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;
}
}
const user = await db.user.findUnique({
const user = await db.user.findFirst({
where: {
email: credentials.email
}
})
OR: [
{ username: credentials.usernameOrEmail.toLowerCase() },
{ email: credentials.usernameOrEmail.toLowerCase() },
],
},
});
if (!user) {
return null
......@@ -71,7 +58,6 @@ export const authOptions: NextAuthOptions = {
id: user.id,
username: user.username,
email: user.email,
name: user.name,
}
}
})
......
import * as z from "zod"
import * as z from "zod";
export const userAuthSchema = z.object({
username: z.string().min(3).max(15),
email: z.string().email(),
password: z.string().min(6).max(18),
})
\ No newline at end of file
usernameOrEmail: z
.union([
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"),
])
.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