From ebbaa839a80de140b2f522bab66427d2e0015e4f Mon Sep 17 00:00:00 2001
From: Caner <s86215@bht-berlin.de>
Date: Tue, 6 Jun 2023 14:19:28 +0200
Subject: [PATCH] fix

---
 .env.example                        |  14 +-
 app/(auth)/login/page.tsx           |  51 +++++--
 app/(auth)/signup/page.tsx          |  62 ++++++--
 app/(content)/(home)/home/page.tsx  |   4 +-
 app/(content)/followers/page.tsx    |  11 +-
 app/api/auth/[...nextauth]/route.ts |  80 +---------
 app/api/likes/likeService.ts        |  12 +-
 app/api/messages/route.ts           |  22 +--
 app/api/route.ts                    |   9 +-
 app/api/signup/route.ts             |  54 +++++--
 app/api/user/[userid].ts            |  28 ----
 app/api/user/index.ts               |  22 ---
 app/layout.tsx                      |   2 +
 components/auth-login-form.tsx      |  69 ---------
 components/auth-signup-form.tsx     |   3 -
 components/icons.tsx                |   8 +-
 components/logo.tsx                 |  10 ++
 components/nav.tsx                  |  22 +--
 components/ui/toast.tsx             |   2 +-
 components/ui/toaster.tsx           |  35 +++++
 components/ui/use-toast.ts          |   4 +-
 components/user-auth-form.tsx       | 224 ++++++++++++++++++++++++++++
 components/user-item.tsx            |   6 +-
 env.mjs                             |  33 ++++
 lib/auth.ts                         |  99 ++++++++++++
 lib/db.ts                           |  23 +--
 lib/igdb.ts                         |   9 +-
 lib/session.ts                      |   9 ++
 lib/utils.ts                        |   4 +-
 lib/validations/auth.ts             |  14 +-
 next.config.js => next.config.mjs   |   6 +-
 package-lock.json                   | 170 +++++++++++++++++----
 package.json                        |  19 ++-
 prisma/schema.prisma                | 109 +++++++++++---
 tsconfig.json                       |   3 +-
 types/next-auth.d.ts                |  22 ++-
 36 files changed, 900 insertions(+), 374 deletions(-)
 delete mode 100644 app/api/user/[userid].ts
 delete mode 100644 app/api/user/index.ts
 delete mode 100644 components/auth-login-form.tsx
 create mode 100644 components/logo.tsx
 create mode 100644 components/ui/toaster.tsx
 create mode 100644 components/user-auth-form.tsx
 create mode 100644 env.mjs
 create mode 100644 lib/auth.ts
 create mode 100644 lib/session.ts
 rename next.config.js => next.config.mjs (61%)

diff --git a/.env.example b/.env.example
index 31b2114..6736807 100644
--- a/.env.example
+++ b/.env.example
@@ -1,16 +1,24 @@
 # Example .env file
 
+# Public App URL
+NEXT_PUBLIC_APP_URL="http://localhost:3000"
+
 # Database for connecting to Prisma
 DATABASE_URL="file:./dev.db"
 
-# Some URLs
+# URLs
 TWITCH_AUTH_BASE_URL="https://id.twitch.tv/oauth2"
 IGDB_BASE_URL="https://api.igdb.com/v4"
 IGDB_IMG_BASE_URL="https://images.igdb.com/igdb/image/upload"
 
-# For Authentication
+# For Twitch Auth to fetch access token
 TWITCH_CLIENT_ID="imdb_client_id"
 TWITCH_CLIENT_SECRET="imdb_auth_id"
 
+# For NextAuth // use `openssl rand -base64 32` to generate a secret
+NEXTAUTH_URL="http://localhost:3000"
 NEXTAUTH_SECRET="secret"
-NEXTAUTH_URL="http://localhost:3000"
\ No newline at end of file
+
+# For Github Auth
+GITHUB_CLIENT_ID="github_client_id"
+GITHUB_CLIENT_SECRET="github_client_secret"
\ No newline at end of file
diff --git a/app/(auth)/login/page.tsx b/app/(auth)/login/page.tsx
index f499915..5cadd64 100644
--- a/app/(auth)/login/page.tsx
+++ b/app/(auth)/login/page.tsx
@@ -1,17 +1,48 @@
-import { LoginForm } from '@/components/auth-login-form'
+import { Icons } from '@/components/icons'
+import { GameUnityLogo } from '@/components/logo'
+import { buttonVariants } from '@/components/ui/button'
+import { UserAuthForm } from '@/components/user-auth-form'
+import { cn } from '@/lib/utils'
 import Link from 'next/link'
 
+export const metadata = {
+    title: "Login",
+    description: "Login to your account",
+}
+
 export default function LoginPage() {
     return (
-        <div className="h-screen w-screen flex justify-center items-center bg-slate-100">
-            <div className="sm:shadow-xl px-8 pb-8 pt-12 sm:bg-black rounded-xl space-y-12">
-                <h1 className="font-semibold text-2xl">Login</h1>
-                <LoginForm />
-                <p className="text-center">
-                    Need to create an account?{' '}
-                    <Link className="text-indigo-500 hover:underline" href="/signup">
-                        Create Account
-                    </Link>{' '}
+        <div className="container flex min-h-screen w-screen flex-col items-center justify-center">
+            <Link
+                href="/"
+                className={cn(
+                    buttonVariants({ variant: "ghost" }),
+                    "absolute left-4 top-4 md:left-8 md:top-8"
+                )}
+            >
+                <>
+                    <Icons.chevronLeft />
+                    Back
+                </>
+            </Link>
+            <div className="mx-auto flex w-full flex-col justify-center space-y-6 sm:w-[350px]">
+                <div className="flex flex-col items-center space-y-2 text-center">
+                    <GameUnityLogo className="h-10 w-10" />
+                    <h1 className="text-2xl font-semibold tracking-tight">
+                        Welcome back
+                    </h1>
+                    <p className="text-sm text-muted-foreground">
+                        Enter your email to sign in to your account
+                    </p>
+                </div>
+                <UserAuthForm type='login' />
+                <p className="px-8 text-center text-sm text-muted-foreground">
+                    <Link
+                        href="/signup"
+                        className="hover:text-brand underline underline-offset-4"
+                    >
+                        Don&apos;t have an account? Sign Up
+                    </Link>
                 </p>
             </div>
         </div>
diff --git a/app/(auth)/signup/page.tsx b/app/(auth)/signup/page.tsx
index ea1b9f6..da3422f 100644
--- a/app/(auth)/signup/page.tsx
+++ b/app/(auth)/signup/page.tsx
@@ -1,18 +1,58 @@
-import { SignupForm } from '@/components/auth-signup-form'
+import { GameUnityLogo } from '@/components/logo'
+import { buttonVariants } from '@/components/ui/button'
+import { UserAuthForm } from '@/components/user-auth-form'
+import { cn } from '@/lib/utils'
 import Link from 'next/link'
 
+export const metadata = {
+    title: "Create an account",
+    description: "Create an account to get started.",
+}
+
 export default function SignupPage() {
     return (
-        <div className="h-screen w-screen flex justify-center items-center bg-slate-100">
-            <div className="sm:shadow-xl px-8 pb-8 pt-12 sm:bg-black rounded-xl space-y-12">
-                <h1 className="font-semibold text-2xl">Create your Account</h1>
-                <SignupForm />
-                <p className="text-center">
-                    Have an account?{' '}
-                    <Link className="text-indigo-500 hover:underline" href="/login">
-                        Sign in
-                    </Link>{' '}
-                </p>
+        <div className="container grid h-screen w-screen flex-col items-center justify-center lg:max-w-none lg:grid-cols-2 lg:px-0">
+            <Link
+                href="/login"
+                className={cn(
+                    buttonVariants({ variant: "ghost" }),
+                    "absolute right-4 top-4 md:right-8 md:top-8"
+                )}
+            >
+                Login
+            </Link>
+            <div className="hidden h-full bg-muted lg:block" />
+            <div className="lg:p-8">
+                <div className="mx-auto flex w-full flex-col justify-center space-y-6 sm:w-[350px]">
+                    <div className="flex flex-col items-center space-y-2 text-center">
+                        <GameUnityLogo className="h-10 w-10" />
+
+                        <h1 className="text-2xl font-semibold tracking-tight">
+                            Create an account
+                        </h1>
+                        <p className="text-sm text-muted-foreground">
+                            Give yourself a username, enter your email and password below to create an account
+                        </p>
+                    </div>
+                    <UserAuthForm type='signup' />
+                    <p className="px-8 text-center text-sm text-muted-foreground">
+                        By clicking continue, you agree to our{" "}
+                        <Link
+                            href="/terms"
+                            className="hover:text-brand underline underline-offset-4"
+                        >
+                            Terms of Service
+                        </Link>{" "}
+                        and{" "}
+                        <Link
+                            href="/privacy"
+                            className="hover:text-brand underline underline-offset-4"
+                        >
+                            Privacy Policy
+                        </Link>
+                        .
+                    </p>
+                </div>
             </div>
         </div>
     )
diff --git a/app/(content)/(home)/home/page.tsx b/app/(content)/(home)/home/page.tsx
index 89b396f..d50aa7c 100644
--- a/app/(content)/(home)/home/page.tsx
+++ b/app/(content)/(home)/home/page.tsx
@@ -1,6 +1,6 @@
 import LikeButton from "@/components/like-button";
 import PostMessageForm from "@/components/post-messages";
-import { prisma } from "@/lib/db";
+import { db } from "@/lib/db";
 import { Prisma } from "@prisma/client";
 /* export const revalidate = 5; */ // revalidate this page every 60 seconds
 
@@ -12,7 +12,7 @@ type messageItemProps = {
 export default async function HomePage() {
   let messages = null
   try {
-    messages = await prisma.post.findMany({
+    messages = await db.post.findMany({
       orderBy: {
         createdAt: "desc"
       }
diff --git a/app/(content)/followers/page.tsx b/app/(content)/followers/page.tsx
index 7fbb300..ec8175d 100644
--- a/app/(content)/followers/page.tsx
+++ b/app/(content)/followers/page.tsx
@@ -1,18 +1,17 @@
-import { authOptions } from "@/app/api/auth/[...nextauth]/route";
 import FollowersList from "@/components/following-users";
 import { getServerSession } from "next-auth";
 
 export default async function Followers() {
-    const session = await getServerSession(authOptions);
+    // const session = await getServerSession(authOptions);
 
-    if (!session) {
-        return <div>Loading...</div>;
-    }
+    // if (!session) {
+    //     return <div>Loading...</div>;
+    // }
 
     return (
         <div>
             <h1>Followers Page WIP</h1>
-            <FollowersList userId={parseFloat(session.user?.id)} />
+            {/* <FollowersList userId={parseFloat(session.user?.id)} /> */}
         </div>
     )
 }
\ No newline at end of file
diff --git a/app/api/auth/[...nextauth]/route.ts b/app/api/auth/[...nextauth]/route.ts
index ca23bf0..17d6a45 100644
--- a/app/api/auth/[...nextauth]/route.ts
+++ b/app/api/auth/[...nextauth]/route.ts
@@ -1,79 +1,7 @@
-import { prisma } from '@/lib/db'
-import { compare } from 'bcrypt'
-import NextAuth, { type NextAuthOptions } from 'next-auth'
-import CredentialsProvider from 'next-auth/providers/credentials'
-
-export const authOptions: NextAuthOptions = {
-    session: {
-        strategy: 'jwt'
-    },
-    providers: [
-        CredentialsProvider({
-            name: 'Sign in',
-            credentials: {
-                email: {
-                    label: 'Email',
-                    type: 'email',
-                    placeholder: 'hello@example.com'
-                },
-                password: { label: 'Password', type: 'password' }
-            },
-            async authorize(credentials) {
-                if (!credentials?.email || !credentials.password) {
-                    return null
-                }
-
-                const user = await prisma.user.findUnique({
-                    where: {
-                        email: credentials.email
-                    }
-                })
-
-                if (!user) {
-                    return null
-                }
-
-                const isPasswordValid = await compare(
-                    credentials.password,
-                    user.password
-                )
-
-                if (!isPasswordValid) {
-                    return null
-                }
-
-                return {
-                    id: user.id + '',
-                    email: user.email,
-                    name: user.name,
-                }
-            }
-        })
-    ],
-    callbacks: {
-        session: ({ session, token }) => {
-            console.log('Session Callback', { session, token })
-            return {
-                ...session,
-                user: {
-                    ...session.user,
-                    id: token.id,
-                }
-            }
-        },
-        jwt: ({ token, user }) => {
-            console.log('JWT Callback', { token, user })
-            if (user) {
-                const u = user as unknown as any
-                return {
-                    ...token,
-                    id: u.id,
-                }
-            }
-            return token
-        }
-    }
-}
+import { authOptions } from '@/lib/auth'
+import NextAuth from 'next-auth'
 
 const handler = NextAuth(authOptions)
+
 export { handler as GET, handler as POST }
+
diff --git a/app/api/likes/likeService.ts b/app/api/likes/likeService.ts
index 0dd748e..4353945 100644
--- a/app/api/likes/likeService.ts
+++ b/app/api/likes/likeService.ts
@@ -1,4 +1,4 @@
-import { prisma } from "@/lib/db"
+import { db } from "@/lib/db"
 import { Prisma } from "@prisma/client"
 
 type likeType = Prisma.LikeUncheckedCreateInput
@@ -12,7 +12,7 @@ export async function putLike(like: likeType): Promise<likeType | undefined> {
     // if exists delete
     // if not create
     try {
-        const actualLike = await prisma.like.findFirst({
+        const actualLike = await db.like.findFirst({
             where: {
                 id: like.id,
                 postId: like.postId,
@@ -25,13 +25,13 @@ export async function putLike(like: likeType): Promise<likeType | undefined> {
             throw Error("Message was not liked by this user")
         }
 
-        await prisma.like.delete({
+        await db.like.delete({
             where: {
                 id: actualLike.id
             }
         })
 
-        const msg = await prisma.post.update({
+        const msg = await db.post.update({
             where: {
                 id: like.postId
             },
@@ -43,14 +43,14 @@ export async function putLike(like: likeType): Promise<likeType | undefined> {
         return undefined;
 
     } catch {
-        const createdLike = await prisma.like.create({
+        const createdLike = await db.like.create({
             data: {
                 postId: like.postId,
                 userId: like.userId
             }
         })
 
-        const updatedMessage = await prisma.post.update({
+        const updatedMessage = await db.post.update({
             where: {
                 id: like.postId
             },
diff --git a/app/api/messages/route.ts b/app/api/messages/route.ts
index c5eecd2..6abf1de 100644
--- a/app/api/messages/route.ts
+++ b/app/api/messages/route.ts
@@ -1,9 +1,9 @@
-import { prisma } from "@/lib/db"
-import { NextRequest, NextResponse } from "next/server"
-import { getServerSession } from "next-auth/next"
-import { authOptions } from "../auth/[...nextauth]/route";
+import { authOptions } from "@/lib/auth";
+import { db } from "@/lib/db";
 import { Prisma } from "@prisma/client";
-import { revalidatePath, revalidateTag } from "next/cache";
+import { getServerSession } from "next-auth/next";
+import { revalidatePath } from "next/cache";
+import { NextRequest, NextResponse } from "next/server";
 
 type post = Prisma.PostUncheckedCreateInput
 
@@ -20,11 +20,11 @@ export async function POST(req: NextRequest) {
 	console.log("router data: " + data.content, "status:")
 
 	try {
-		await prisma.post.create({
+		await db.post.create({
 			/* data: data */
-			data:{
+			data: {
 				content: data.content,
-				userId: parseInt(userId),
+				userId: userId,
 				published: true
 			}
 		})
@@ -34,7 +34,7 @@ export async function POST(req: NextRequest) {
 
 		return NextResponse.json({ status: 201, message: 'Message Created' })
 
-	} catch (error) {
+	} catch (error: any) {
 		console.log("fail" + error);
 	}
 	console.log("post")
@@ -45,11 +45,11 @@ export async function GET(req: NextRequest, res: NextResponse) {
 		const data = await req.json()
 		console.log("router data: " + data, "status:")
 	} catch (error) {
-		
+
 	}
 
 	try {
-		const messages = await prisma.post.findMany({
+		const messages = await db.post.findMany({
 			orderBy: {
 				createdAt: "desc"
 			}
diff --git a/app/api/route.ts b/app/api/route.ts
index 712fbf8..ae6ece8 100644
--- a/app/api/route.ts
+++ b/app/api/route.ts
@@ -1,16 +1,13 @@
+import { authOptions } from '@/lib/auth'
 import { getServerSession } from 'next-auth/next'
 import { NextResponse } from 'next/server'
-import { authOptions } from './auth/[...nextauth]/route'
 
-export async function GET(request: Request) {
+export async function GET() {
     const session = await getServerSession(authOptions)
 
     if (!session) {
-        return new NextResponse(JSON.stringify({ error: 'unauthorized' }), {
-            status: 401
-        })
+        return new NextResponse(JSON.stringify({ error: 'unauthorized' }), { status: 401 })
     }
 
-    console.log('GET API', session)
     return NextResponse.json({ authenticated: !!session })
 }
\ No newline at end of file
diff --git a/app/api/signup/route.ts b/app/api/signup/route.ts
index 79937ac..124f2fd 100644
--- a/app/api/signup/route.ts
+++ b/app/api/signup/route.ts
@@ -1,32 +1,64 @@
-import { prisma } from '@/lib/db'
+import { db } from '@/lib/db'
 import { hash } from 'bcrypt'
 import { NextResponse } from 'next/server'
 
 export async function POST(req: Request) {
     try {
-        const { email, password } = await req.json()
+        const { username, email, password } = await req.json()
         const hashed = await hash(password, 12)
 
-        const user = await prisma.user.create({
+        let usernameCheck = username.toLowerCase()
+        const emailCheck = email.toLowerCase()
+
+        const existingUser = await db.user.findUnique({
+            where: {
+                email: emailCheck
+            }
+        })
+
+        if (existingUser) {
+            throw new Error('email already exists')
+        }
+
+        let isUnique = false;
+        while (!isUnique) {
+            const existingUserName = await db.user.findUnique({
+                where: {
+                    username: usernameCheck
+                }
+            })
+
+            if (existingUserName) {
+                usernameCheck = `${username}${Math.floor(Math.random() * 1000)}`
+            } else {
+                isUnique = true;
+            }
+        }
+
+        const user = await db.user.create({
             data: {
-                email,
+                name: username,
+                username: usernameCheck,
+                email: emailCheck,
                 password: hashed
             }
         })
 
         return NextResponse.json({
-            user: {
-                email: user.email
-            }
+            usernameOrEmail: user.email
         })
     } catch (err: any) {
+        if (err.message === 'email already exists') {
+            return new NextResponse(JSON.stringify({
+                error: err.message
+            }), { status: 422 }
+            )
+        }
+
         return new NextResponse(
             JSON.stringify({
                 error: err.message
-            }),
-            {
-                status: 500
-            }
+            }), { status: 500 }
         )
     }
 }
\ No newline at end of file
diff --git a/app/api/user/[userid].ts b/app/api/user/[userid].ts
deleted file mode 100644
index 5fa1e7c..0000000
--- a/app/api/user/[userid].ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { NextApiRequest, NextApiResponse } from "next";
-
-import { prisma } from "@/lib/db";
-
-export default async function handler(req: NextApiRequest, res: NextApiResponse) {
-  if (req.method !== 'GET') {
-    return res.status(405).end();
-  }
-
-  try {
-    const { userId } = req.query;
-
-    if (!userId || typeof userId !== 'string') {
-      throw new Error('Invalid ID');
-    }
-
-    const existingUser = await prisma.user.findUnique({
-      where: {
-        id : +userId
-      }
-    });
-
-    return res.status(200).json({ ...existingUser});
-  } catch (error) {
-    console.log(error);
-    return res.status(400).end();
-  }
-};
\ No newline at end of file
diff --git a/app/api/user/index.ts b/app/api/user/index.ts
deleted file mode 100644
index abb35ff..0000000
--- a/app/api/user/index.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { NextApiRequest, NextApiResponse } from "next";
-
-import { prisma } from "@/lib/db";
-
-export default async function handler(req: NextApiRequest, res: NextApiResponse) {
-  if (req.method !== 'GET') {
-    return res.status(405).end();
-  }
-
-  try {
-    const users = await prisma.user.findMany({
-      orderBy: {
-        createdAt: 'desc'
-      }
-    });
-
-    return res.status(200).json(users);
-  } catch(error) {
-    console.log(error);
-    return res.status(400).end();
-  }
-}
\ No newline at end of file
diff --git a/app/layout.tsx b/app/layout.tsx
index f53ce33..421584b 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -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>
diff --git a/components/auth-login-form.tsx b/components/auth-login-form.tsx
deleted file mode 100644
index 0b430e2..0000000
--- a/components/auth-login-form.tsx
+++ /dev/null
@@ -1,69 +0,0 @@
-'use client'
-
-import { Alert } from '@/components/ui/alert'
-import { Button } from '@/components/ui/button'
-import { Input } from '@/components/ui/input'
-import { Label } from '@/components/ui/label'
-import { signIn } from 'next-auth/react'
-import { useRouter, useSearchParams } from 'next/navigation'
-import { useState } from 'react'
-
-export const LoginForm = () => {
-    const router = useRouter()
-    const searchParams = useSearchParams()
-    const callbackUrl = searchParams.get('callbackUrl') || '/home'
-    const [email, setEmail] = useState('')
-    const [password, setPassword] = useState('')
-    const [error, setError] = useState('')
-
-    const onSubmit = async (e: React.FormEvent) => {
-        e.preventDefault()
-        try {
-            const res = await signIn('credentials', {
-                redirect: false,
-                email,
-                password,
-                callbackUrl
-            })
-            console.log('Res', res)
-            if (!res?.error) {
-                router.push(callbackUrl)
-            } else {
-                setError('Invalid email or password')
-            }
-        } catch (err: any) { }
-    }
-
-    return (
-        <form onSubmit={onSubmit} className="space-y-12 w-full sm:w-[400px]">
-            <div className="grid w-full items-center gap-1.5">
-                <Label htmlFor="email">Email</Label>
-                <Input
-                    className="w-full"
-                    required
-                    value={email}
-                    onChange={(e) => setEmail(e.target.value)}
-                    id="email"
-                    type="email"
-                />
-            </div>
-            <div className="grid w-full items-center gap-1.5">
-                <Label htmlFor="password">Password</Label>
-                <Input
-                    className="w-full"
-                    required
-                    value={password}
-                    onChange={(e) => setPassword(e.target.value)}
-                    id="password"
-                    type="password"
-                />
-            </div>
-            {error && <Alert>{error}</Alert>}
-            <div className="w-full">
-                <Button className="w-full" size="lg">
-                    Login
-                </Button>
-            </div>
-        </form>
-    )
-}
\ No newline at end of file
diff --git a/components/auth-signup-form.tsx b/components/auth-signup-form.tsx
index 1c1cb16..1d95dcd 100644
--- a/components/auth-signup-form.tsx
+++ b/components/auth-signup-form.tsx
@@ -5,13 +5,11 @@ import { Button } from '@/components/ui/button'
 import { Input } from '@/components/ui/input'
 import { Label } from '@/components/ui/label'
 import { useState } from 'react'
-import { useRouter } from 'next/navigation'
 
 export const SignupForm = () => {
     const [email, setEmail] = useState('')
     const [password, setPassword] = useState('')
     const [error, setError] = useState<string | null>(null)
-    const router = useRouter(); 
 
     const onSubmit = async (e: React.FormEvent) => {
         e.preventDefault()
@@ -28,7 +26,6 @@ export const SignupForm = () => {
                 }
             })
             if (res.ok) {
-                router.push("/login")
             } else {
                 setError((await res.json()).error)
             }
diff --git a/components/icons.tsx b/components/icons.tsx
index e9a8c6b..6704d78 100644
--- a/components/icons.tsx
+++ b/components/icons.tsx
@@ -11,6 +11,7 @@ import {
     File,
     FileText,
     Gamepad2,
+    Github,
     Heart,
     HelpCircle,
     Home,
@@ -71,12 +72,13 @@ export const Icons: IconsType = {
     help: HelpCircle, // Help Nav
     sun: SunMedium, // Light Mode Toggle Nav
     moon: Moon, // Dark Mode Toggle Nav
+    arrowupline: ArrowUpToLine, // Back to Top Button with line
     arrowdown: ArrowDown, // Descending Sort
     heart: Heart, // Like Button
-    arrowupline: ArrowUpToLine, // Back to Top Button
+    chevronLeft: ChevronLeft, // Back Login Arrow
+    spinner: Loader2, // Loading Spinner
+    github: Github, // Github Icon
     close: X,
-    spinner: Loader2,
-    chevronLeft: ChevronLeft,
     chevronRight: ChevronRight,
     trash: Trash,
     post: FileText,
diff --git a/components/logo.tsx b/components/logo.tsx
new file mode 100644
index 0000000..145e3c7
--- /dev/null
+++ b/components/logo.tsx
@@ -0,0 +1,10 @@
+import { Icons } from "./icons";
+
+export function GameUnityLogo({ className }: { className?: string }) {
+    return (
+        <>
+            <Icons.logo className={`dark:hidden ${className}`} />
+            <Icons.logoWhite className={`hidden dark:block ${className}`} />
+        </>
+    )
+}
diff --git a/components/nav.tsx b/components/nav.tsx
index 45b8874..e4743b8 100644
--- a/components/nav.tsx
+++ b/components/nav.tsx
@@ -4,10 +4,11 @@ import { Icons, IconsType } from "@/components/icons";
 import { buttonVariants } from "@/components/ui/button";
 import { cn } from "@/lib/utils";
 import { SidebarNavItem } from "@/types";
+import { signIn, signOut, useSession } from "next-auth/react";
 import Link from "next/link";
 import { usePathname } from "next/navigation";
+import { GameUnityLogo } from "./logo";
 import { ModeToggle } from "./mode-toggle";
-import {signIn, signOut, useSession } from "next-auth/react"
 
 interface DashboardNavProps {
     items: SidebarNavItem[]
@@ -19,7 +20,7 @@ export default function DashboardNav({ items }: DashboardNavProps) {
     if (!items?.length) {
         return null
     }
-    
+
     const isLoaded = true
     const user = "test"
 
@@ -27,8 +28,7 @@ export default function DashboardNav({ items }: DashboardNavProps) {
         <nav className="grid items-start gap-2">
             <div className="flex items-center">
                 <Link href="/" className={cn("rounded-full p-3 hover:bg-accent")}>
-                    <Icons.logo className="h-7 w-7 dark:hidden" />
-                    <Icons.logoWhite className="h-7 w-7 hidden dark:block" />
+                    <GameUnityLogo className="h-8 w-8" />
                 </Link>
             </div>
             {session?.user && isLoaded && user ?
@@ -64,14 +64,14 @@ export default function DashboardNav({ items }: DashboardNavProps) {
                 </div>
             }
             {session?.user &&
-            <>
-            <p className="text-sky-600"> {session?.user.name}</p>
-            <button className=" text-red-500" onClick={() => signOut()}>
-                Sign Out
-            </button>
-            </>
+                <>
+                    <p className="text-sky-600"> {session?.user.name}</p>
+                    <button className=" text-red-500" onClick={() => signOut()}>
+                        Sign Out
+                    </button>
+                </>
             }
-        <ModeToggle />
+            <ModeToggle />
         </nav>
     )
 }
\ No newline at end of file
diff --git a/components/ui/toast.tsx b/components/ui/toast.tsx
index 2deb2e9..f820588 100644
--- a/components/ui/toast.tsx
+++ b/components/ui/toast.tsx
@@ -1,6 +1,6 @@
 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"
diff --git a/components/ui/toaster.tsx b/components/ui/toaster.tsx
new file mode 100644
index 0000000..ac9370c
--- /dev/null
+++ b/components/ui/toaster.tsx
@@ -0,0 +1,35 @@
+"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
diff --git a/components/ui/use-toast.ts b/components/ui/use-toast.ts
index c70c0d6..2c94c2d 100644
--- a/components/ui/use-toast.ts
+++ b/components/ui/use-toast.ts
@@ -1,7 +1,7 @@
 // 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()
diff --git a/components/user-auth-form.tsx b/components/user-auth-form.tsx
new file mode 100644
index 0000000..f4f2072
--- /dev/null
+++ b/components/user-auth-form.tsx
@@ -0,0 +1,224 @@
+'use client'
+
+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"
+
+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 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: true,
+            callbackUrl: searchParams?.get("from") || "/home",
+        });
+
+        setIsLoading(false)
+
+        if (signInResult?.error) {
+            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>,
+            })
+        }
+
+        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">
+                    {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={() => {
+                    setIsGitHubLoading(true)
+                    signIn("github", { callbackUrl: searchParams?.get("from") || "/home" })
+                }}
+                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
diff --git a/components/user-item.tsx b/components/user-item.tsx
index 0da145d..64c729d 100644
--- a/components/user-item.tsx
+++ b/components/user-item.tsx
@@ -3,19 +3,19 @@ import Link from "next/link";
 import FollowButton from "./following-button";
 
 // this is a single user helper-component, only for design purposes
-export default function FollowUser({ id, followId, userName, image }: { id: number, followId: number, userName: string, image: { url: string } }) {
+export default function FollowUser({ id, followId, username, image }: { id: number, followId: number, username: string, image: { url: string } }) {
     return (
         <div>
             <Link href={`/user/${id}`}>
                 <div className="">
                     <Image
                         src={image.url}
-                        alt={userName}
+                        alt={username}
                         width={50}
                         height={50}
                         priority={true} />
                 </div>
-                <p>{userName}</p>
+                <p>{username}</p>
                 <FollowButton userId={id} followerId={followId} />
             </Link>
         </div>
diff --git a/env.mjs b/env.mjs
new file mode 100644
index 0000000..0154d5d
--- /dev/null
+++ b/env.mjs
@@ -0,0 +1,33 @@
+import { createEnv } from "@t3-oss/env-nextjs"
+import { z } from "zod"
+
+export const env = createEnv({
+    server: {
+        DATABASE_URL: z.string().min(1),
+        GITHUB_CLIENT_ID: z.string().min(1),
+        GITHUB_CLIENT_SECRET: z.string().min(1),
+        NEXTAUTH_URL: z.string().url().optional(),
+        NEXTAUTH_SECRET: z.string().min(1),
+        TWITCH_CLIENT_ID: z.string().min(1),
+        TWITCH_CLIENT_SECRET: z.string().min(1),
+        TWITCH_AUTH_BASE_URL: z.string().url().optional(),
+        IGDB_BASE_URL: z.string().url().optional(),
+        IGDB_IMG_BASE_URL: z.string().url().optional(),
+    },
+    client: {
+        NEXT_PUBLIC_APP_URL: z.string().min(1),
+    },
+    runtimeEnv: {
+        DATABASE_URL: process.env.DATABASE_URL,
+        GITHUB_CLIENT_ID: process.env.GITHUB_CLIENT_ID,
+        GITHUB_CLIENT_SECRET: process.env.GITHUB_CLIENT_SECRET,
+        NEXTAUTH_URL: process.env.NEXTAUTH_URL,
+        NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET,
+        NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
+        TWITCH_CLIENT_ID: process.env.TWITCH_CLIENT_ID,
+        TWITCH_CLIENT_SECRET: process.env.TWITCH_CLIENT_SECRET,
+        TWITCH_AUTH_BASE_URL: process.env.TWITCH_AUTH_BASE_URL,
+        IGDB_BASE_URL: process.env.IGDB_BASE_URL,
+        IGDB_IMG_BASE_URL: process.env.IGDB_IMG_BASE_URL,
+    },
+})
\ No newline at end of file
diff --git a/lib/auth.ts b/lib/auth.ts
new file mode 100644
index 0000000..7763e34
--- /dev/null
+++ b/lib/auth.ts
@@ -0,0 +1,99 @@
+import { env } from "@/env.mjs"
+import { db } from "@/lib/db"
+import { PrismaAdapter } from "@auth/prisma-adapter"
+import { compare } from "bcrypt"
+import { NextAuthOptions } from "next-auth"
+import { Adapter } from "next-auth/adapters"
+import CredentialsProvider from 'next-auth/providers/credentials'
+import GitHubProvider from "next-auth/providers/github"
+
+export const authOptions: NextAuthOptions = {
+    adapter: PrismaAdapter(db as any) as Adapter,
+    session: {
+        strategy: 'jwt'
+    },
+    pages: {
+        signIn: "/login",
+    },
+    providers: [
+        GitHubProvider({
+            clientId: env.GITHUB_CLIENT_ID,
+            clientSecret: env.GITHUB_CLIENT_SECRET,
+        }),
+
+        CredentialsProvider({
+            name: 'Login',
+            credentials: {
+                usernameOrEmail: { label: 'Username or Email', type: 'text' },
+                password: { label: 'Password', type: 'password' }
+            },
+            async authorize(credentials) {
+                if (!credentials?.usernameOrEmail || !credentials?.password) {
+                    return null
+                }
+
+                const user = await db.user.findFirst({
+                    where: {
+                        OR: [
+                            { username: credentials.usernameOrEmail.toLowerCase() },
+                            { email: credentials.usernameOrEmail.toLowerCase() },
+                        ],
+                    },
+                });
+
+                if (!user || !user.password) {
+                    return null
+                }
+
+                const isPasswordValid = await compare(
+                    credentials.password,
+                    user.password
+                )
+
+                if (!isPasswordValid) {
+                    return null
+                }
+
+                return {
+                    id: user.id,
+                    username: user.username,
+                    email: user.email,
+                }
+            }
+        })
+    ],
+    secret: env.NEXTAUTH_SECRET,
+    callbacks: {
+        async session({ token, session }) {
+            if (token) {
+                session.user.id = token.id
+                session.user.name = token.name
+                session.user.email = token.email
+                session.user.image = token.picture
+            }
+
+            return session
+        },
+        async jwt({ token, user }) {
+            const dbUser = await db.user.findFirst({
+                where: {
+                    email: token.email,
+                },
+            })
+
+            if (!dbUser) {
+                if (user) {
+                    token.id = user?.id
+                }
+                return token
+            }
+
+            return {
+                id: dbUser.id,
+                name: dbUser.name,
+                email: dbUser.email,
+                picture: dbUser.image,
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/lib/db.ts b/lib/db.ts
index e67728c..8a33a38 100644
--- a/lib/db.ts
+++ b/lib/db.ts
@@ -1,13 +1,18 @@
-import { PrismaClient } from '@prisma/client'
+import { PrismaClient } from "@prisma/client"
 
-const globalForPrisma = global as unknown as {
-  prisma: PrismaClient | undefined
+declare global {
+  // eslint-disable-next-line no-var
+  var cachedPrisma: PrismaClient
 }
 
-export const prisma =
-  globalForPrisma.prisma ??
-  new PrismaClient({
-    // log: ['query'],
-  })
+let prisma: PrismaClient
+if (process.env.NODE_ENV === "production") {
+  prisma = new PrismaClient()
+} else {
+  if (!global.cachedPrisma) {
+    global.cachedPrisma = new PrismaClient()
+  }
+  prisma = global.cachedPrisma
+}
 
-if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
\ No newline at end of file
+export const db = prisma
\ No newline at end of file
diff --git a/lib/igdb.ts b/lib/igdb.ts
index d48e98e..93d0d43 100644
--- a/lib/igdb.ts
+++ b/lib/igdb.ts
@@ -1,11 +1,12 @@
+import { env } from "@/env.mjs"
 import { calculateOffset, getImageURL } from "@/lib/utils"
 import { IAuth, IGame } from "@/types/igdb-types"
 
-const TWITCH_AUTH_BASE_URL = process.env.TWITCH_AUTH_BASE_URL ?? ''
-const IGDB_BASE_URL = process.env.IGDB_BASE_URL ?? ''
+const TWITCH_AUTH_BASE_URL = env.TWITCH_AUTH_BASE_URL ?? ''
+const IGDB_BASE_URL = env.IGDB_BASE_URL ?? ''
 
-const CLIENT_ID = process.env.TWITCH_CLIENT_ID ?? ''
-const CLIENT_SECRET = process.env.TWITCH_CLIENT_SECRET ?? ''
+const CLIENT_ID = env.TWITCH_CLIENT_ID ?? ''
+const CLIENT_SECRET = env.TWITCH_CLIENT_SECRET ?? ''
 
 const limit = 100
 
diff --git a/lib/session.ts b/lib/session.ts
new file mode 100644
index 0000000..1bc9e23
--- /dev/null
+++ b/lib/session.ts
@@ -0,0 +1,9 @@
+import { getServerSession } from "next-auth/next"
+
+import { authOptions } from "@/lib/auth"
+
+export async function getCurrentUser() {
+    const session = await getServerSession(authOptions)
+
+    return session?.user
+}
\ No newline at end of file
diff --git a/lib/utils.ts b/lib/utils.ts
index 6a89161..3d7f8f6 100644
--- a/lib/utils.ts
+++ b/lib/utils.ts
@@ -1,3 +1,4 @@
+import { env } from "@/env.mjs"
 import { ClassValue, clsx } from "clsx"
 import { twMerge } from "tailwind-merge"
 
@@ -6,10 +7,9 @@ export function cn(...inputs: ClassValue[]) {
   return twMerge(clsx(inputs))
 }
 
-const IGDB_IMG_BASE_URL = process.env.IGDB_IMG_BASE_URL ?? ''
-
 // changes the default size of the image to be fetched
 export function getImageURL(hashId: string, size: string): string {
+  const IGDB_IMG_BASE_URL = env.IGDB_IMG_BASE_URL ?? ''
   return `${IGDB_IMG_BASE_URL}/t_${size}/${hashId}.jpg`
 }
 
diff --git a/lib/validations/auth.ts b/lib/validations/auth.ts
index e65b5cb..e749cbd 100644
--- a/lib/validations/auth.ts
+++ b/lib/validations/auth.ts
@@ -1,5 +1,13 @@
-import * as z from "zod"
+import * as z from "zod";
 
 export const userAuthSchema = z.object({
-    email: z.string().email(),
-})
\ 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
diff --git a/next.config.js b/next.config.mjs
similarity index 61%
rename from next.config.js
rename to next.config.mjs
index ce446f9..be988d3 100644
--- a/next.config.js
+++ b/next.config.mjs
@@ -1,9 +1,13 @@
+import "./env.mjs"
+
 /** @type {import('next').NextConfig} */
 const nextConfig = {
+    reactStrictMode: true,
+    swcMinify: true,
     images: {
         unoptimized: true,
         domains: ["images.igdb.com"]
     }
 }
 
-module.exports = nextConfig
+export default nextConfig
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index d7fdf5d..f3aa7f9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,6 +8,8 @@
       "name": "project_ss23_gameunity",
       "version": "0.2.0",
       "dependencies": {
+        "@auth/prisma-adapter": "^1.0.0",
+        "@hookform/resolvers": "^3.1.0",
         "@prisma/client": "^4.15.0",
         "@radix-ui/react-dropdown-menu": "^2.0.5",
         "@radix-ui/react-label": "^2.0.2",
@@ -15,33 +17,36 @@
         "@radix-ui/react-select": "^1.2.2",
         "@radix-ui/react-slot": "^1.0.2",
         "@radix-ui/react-toast": "^1.1.4",
+        "@t3-oss/env-nextjs": "^0.4.0",
         "@tanstack/react-query": "^4.29.12",
         "bcrypt": "^5.1.0",
         "class-variance-authority": "^0.6.0",
         "clsx": "^1.2.1",
-        "lucide-react": "^0.224.0",
+        "lucide-react": "^0.234.0",
         "next": "^13.4.4",
         "next-auth": "^4.22.1",
         "next-themes": "^0.2.1",
         "react": "18.2.0",
         "react-dom": "18.2.0",
+        "react-hook-form": "^7.44.3",
         "react-infinite-scroll-component": "^6.1.0",
-        "tailwind-merge": "^1.12.0",
-        "tailwindcss-animate": "^1.0.5"
+        "tailwind-merge": "^1.13.0",
+        "tailwindcss-animate": "^1.0.5",
+        "zod": "^3.21.4"
       },
       "devDependencies": {
         "@tanstack/eslint-plugin-query": "^4.29.9",
         "@types/bcrypt": "^5.0.0",
         "@types/node": "^20.2.5",
-        "@types/react": "^18.2.7",
+        "@types/react": "^18.2.8",
         "@types/react-dom": "^18.2.4",
         "autoprefixer": "10.4.14",
-        "eslint": "^8.41.0",
+        "eslint": "^8.42.0",
         "eslint-config-next": "^13.4.4",
         "postcss": "8.4.24",
         "prisma": "^4.15.0",
         "tailwindcss": "3.3.2",
-        "typescript": "^5.0.4"
+        "typescript": "^5.1.3"
       }
     },
     "node_modules/@alloc/quick-lru": {
@@ -55,6 +60,58 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/@auth/core": {
+      "version": "0.8.1",
+      "resolved": "https://registry.npmjs.org/@auth/core/-/core-0.8.1.tgz",
+      "integrity": "sha512-WudBmZudZ/cvykxHV5hIwrYsd7AlETQ535O7w3sSiiumT28+U9GvBb8oSRtfzxpW9rym3lAdfeTJqGA8U4FecQ==",
+      "dependencies": {
+        "@panva/hkdf": "^1.0.4",
+        "cookie": "0.5.0",
+        "jose": "^4.11.1",
+        "oauth4webapi": "^2.0.6",
+        "preact": "10.11.3",
+        "preact-render-to-string": "5.2.3"
+      },
+      "peerDependencies": {
+        "nodemailer": "^6.8.0"
+      },
+      "peerDependenciesMeta": {
+        "nodemailer": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@auth/core/node_modules/preact": {
+      "version": "10.11.3",
+      "resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz",
+      "integrity": "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==",
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/preact"
+      }
+    },
+    "node_modules/@auth/core/node_modules/preact-render-to-string": {
+      "version": "5.2.3",
+      "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.3.tgz",
+      "integrity": "sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==",
+      "dependencies": {
+        "pretty-format": "^3.8.0"
+      },
+      "peerDependencies": {
+        "preact": ">=10"
+      }
+    },
+    "node_modules/@auth/prisma-adapter": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/@auth/prisma-adapter/-/prisma-adapter-1.0.0.tgz",
+      "integrity": "sha512-+x+s5dgpNmqrcQC2ZRAXZIM6yhkWP/EXjIUgqUyMepLiX1OHi2AXIUAAbXsW4oG9OpYr/rvPIzPBpuGt6sPFwQ==",
+      "dependencies": {
+        "@auth/core": "0.8.1"
+      },
+      "peerDependencies": {
+        "@prisma/client": ">=2.26.0 || >=3 || >=4"
+      }
+    },
     "node_modules/@babel/runtime": {
       "version": "7.21.5",
       "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.5.tgz",
@@ -114,9 +171,9 @@
       }
     },
     "node_modules/@eslint/js": {
-      "version": "8.41.0",
-      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.41.0.tgz",
-      "integrity": "sha512-LxcyMGxwmTh2lY9FwHPGWOHmYFCZvbrFCBZL4FzSSsxsRPuhrYUg/49/0KDfW8tnIEaEHtfmn6+NPN+1DqaNmA==",
+      "version": "8.42.0",
+      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.42.0.tgz",
+      "integrity": "sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw==",
       "dev": true,
       "engines": {
         "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -147,10 +204,18 @@
         "react-dom": ">=16.8.0"
       }
     },
+    "node_modules/@hookform/resolvers": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.1.0.tgz",
+      "integrity": "sha512-z0A8K+Nxq+f83Whm/ajlwE6VtQlp/yPHZnXw7XWVPIGm1Vx0QV8KThU3BpbBRfAZ7/dYqCKKBNnQh85BkmBKkA==",
+      "peerDependencies": {
+        "react-hook-form": "^7.0.0"
+      }
+    },
     "node_modules/@humanwhocodes/config-array": {
-      "version": "0.11.8",
-      "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz",
-      "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==",
+      "version": "0.11.10",
+      "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz",
+      "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==",
       "dev": true,
       "dependencies": {
         "@humanwhocodes/object-schema": "^1.2.1",
@@ -1210,6 +1275,27 @@
         "tslib": "^2.4.0"
       }
     },
+    "node_modules/@t3-oss/env-core": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/@t3-oss/env-core/-/env-core-0.4.0.tgz",
+      "integrity": "sha512-6JlMp0Vru15q/axHzBKsQQjiyGS6k+EsZBY1iErGVmOGzNSoVluBahnYFP7tEkwZ7KoRgSq4NRIc1Ez7SVYuxQ==",
+      "peerDependencies": {
+        "typescript": ">=4.7.2",
+        "zod": "^3.0.0"
+      }
+    },
+    "node_modules/@t3-oss/env-nextjs": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/@t3-oss/env-nextjs/-/env-nextjs-0.4.0.tgz",
+      "integrity": "sha512-K1u2i+S/uEhjfg++FqWlOzS6x237EARRbWGowH2MkDkFu2q7ZJSiJBJT8e47L7NHWH5IyZrTCM6BdOxyWEnQuQ==",
+      "dependencies": {
+        "@t3-oss/env-core": "0.4.0"
+      },
+      "peerDependencies": {
+        "typescript": ">=4.7.2",
+        "zod": "^3.0.0"
+      }
+    },
     "node_modules/@tanstack/eslint-plugin-query": {
       "version": "4.29.9",
       "resolved": "https://registry.npmjs.org/@tanstack/eslint-plugin-query/-/eslint-plugin-query-4.29.9.tgz",
@@ -1283,9 +1369,9 @@
       "devOptional": true
     },
     "node_modules/@types/react": {
-      "version": "18.2.7",
-      "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.7.tgz",
-      "integrity": "sha512-ojrXpSH2XFCmHm7Jy3q44nXDyN54+EYKP2lBhJ2bqfyPj6cIUW/FZW/Csdia34NQgq7KYcAlHi5184m4X88+yw==",
+      "version": "18.2.8",
+      "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.8.tgz",
+      "integrity": "sha512-lTyWUNrd8ntVkqycEEplasWy2OxNlShj3zqS0LuB1ENUGis5HodmhM7DtCoUGbxj3VW/WsGA0DUhpG6XrM7gPA==",
       "devOptional": true,
       "dependencies": {
         "@types/prop-types": "*",
@@ -2381,16 +2467,16 @@
       }
     },
     "node_modules/eslint": {
-      "version": "8.41.0",
-      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.41.0.tgz",
-      "integrity": "sha512-WQDQpzGBOP5IrXPo4Hc0814r4/v2rrIsB0rhT7jtunIalgg6gYXWhRMOejVO8yH21T/FGaxjmFjBMNqcIlmH1Q==",
+      "version": "8.42.0",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.42.0.tgz",
+      "integrity": "sha512-ulg9Ms6E1WPf67PHaEY4/6E2tEn5/f7FXGzr3t9cBMugOmf1INYvuUwwh1aXQN4MfJ6a5K2iNwP3w4AColvI9A==",
       "dev": true,
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.2.0",
         "@eslint-community/regexpp": "^4.4.0",
         "@eslint/eslintrc": "^2.0.3",
-        "@eslint/js": "8.41.0",
-        "@humanwhocodes/config-array": "^0.11.8",
+        "@eslint/js": "8.42.0",
+        "@humanwhocodes/config-array": "^0.11.10",
         "@humanwhocodes/module-importer": "^1.0.1",
         "@nodelib/fs.walk": "^1.2.8",
         "ajv": "^6.10.0",
@@ -3961,9 +4047,9 @@
       }
     },
     "node_modules/lucide-react": {
-      "version": "0.224.0",
-      "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.224.0.tgz",
-      "integrity": "sha512-2QuPhbEAicN1Ak9DSeViYhZLYogW54e802IFDasoy/AXKGrnzTcBLQM8gidXbOd2DSWbrLHDgN6n4340QzyujQ==",
+      "version": "0.234.0",
+      "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.234.0.tgz",
+      "integrity": "sha512-7MtwK9zPXyvpHv9Tf0anraIjI9yRDdkfYIPt8KOC54NvimBAxbNpeIBb5/f+tZVIkN0hMgZdrAFugZe4YPZHcA==",
       "peerDependencies": {
         "react": "^16.5.1 || ^17.0.0 || ^18.0.0"
       }
@@ -4341,6 +4427,14 @@
       "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz",
       "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA=="
     },
+    "node_modules/oauth4webapi": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-2.3.0.tgz",
+      "integrity": "sha512-JGkb5doGrwzVDuHwgrR4nHJayzN4h59VCed6EW8Tql6iHDfZIabCJvg6wtbn5q6pyB2hZruI3b77Nudvq7NmvA==",
+      "funding": {
+        "url": "https://github.com/sponsors/panva"
+      }
+    },
     "node_modules/object-assign": {
       "version": "4.1.1",
       "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -4909,6 +5003,21 @@
         "react": "^18.2.0"
       }
     },
+    "node_modules/react-hook-form": {
+      "version": "7.44.3",
+      "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.44.3.tgz",
+      "integrity": "sha512-/tHId6p2ViAka1wECMw8FEPn/oz/w226zehHrJyQ1oIzCBNMIJCaj6ZkQcv+MjDxYh9MWR7RQic7Qqwe4a5nkw==",
+      "engines": {
+        "node": ">=12.22.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/react-hook-form"
+      },
+      "peerDependencies": {
+        "react": "^16.8.0 || ^17 || ^18"
+      }
+    },
     "node_modules/react-infinite-scroll-component": {
       "version": "6.1.0",
       "resolved": "https://registry.npmjs.org/react-infinite-scroll-component/-/react-infinite-scroll-component-6.1.0.tgz",
@@ -5594,9 +5703,9 @@
       }
     },
     "node_modules/tailwind-merge": {
-      "version": "1.12.0",
-      "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.12.0.tgz",
-      "integrity": "sha512-Y17eDp7FtN1+JJ4OY0Bqv9OA41O+MS8c1Iyr3T6JFLnOgLg3EvcyMKZAnQ8AGyvB5Nxm3t9Xb5Mhe139m8QT/g==",
+      "version": "1.13.0",
+      "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.13.0.tgz",
+      "integrity": "sha512-mUTmDbcU+IhOvJ0c42eLQ/nRkvolTqfpVaVQRSxfJAv9TabS6Y2zW/1wKpKLdKzyL3Gh8j6NTLl6MWNmvOM6kA==",
       "funding": {
         "type": "github",
         "url": "https://github.com/sponsors/dcastil"
@@ -5815,16 +5924,15 @@
       }
     },
     "node_modules/typescript": {
-      "version": "5.0.4",
-      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz",
-      "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==",
-      "devOptional": true,
+      "version": "5.1.3",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz",
+      "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==",
       "bin": {
         "tsc": "bin/tsc",
         "tsserver": "bin/tsserver"
       },
       "engines": {
-        "node": ">=12.20"
+        "node": ">=14.17"
       }
     },
     "node_modules/unbox-primitive": {
diff --git a/package.json b/package.json
index 3c968f4..926c0c1 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,8 @@
     "preview": "next build && next start"
   },
   "dependencies": {
+    "@auth/prisma-adapter": "^1.0.0",
+    "@hookform/resolvers": "^3.1.0",
     "@prisma/client": "^4.15.0",
     "@radix-ui/react-dropdown-menu": "^2.0.5",
     "@radix-ui/react-label": "^2.0.2",
@@ -18,32 +20,35 @@
     "@radix-ui/react-select": "^1.2.2",
     "@radix-ui/react-slot": "^1.0.2",
     "@radix-ui/react-toast": "^1.1.4",
+    "@t3-oss/env-nextjs": "^0.4.0",
     "@tanstack/react-query": "^4.29.12",
     "bcrypt": "^5.1.0",
     "class-variance-authority": "^0.6.0",
     "clsx": "^1.2.1",
-    "lucide-react": "^0.224.0",
+    "lucide-react": "^0.234.0",
     "next": "^13.4.4",
     "next-auth": "^4.22.1",
     "next-themes": "^0.2.1",
     "react": "18.2.0",
     "react-dom": "18.2.0",
+    "react-hook-form": "^7.44.3",
     "react-infinite-scroll-component": "^6.1.0",
-    "tailwind-merge": "^1.12.0",
-    "tailwindcss-animate": "^1.0.5"
+    "tailwind-merge": "^1.13.0",
+    "tailwindcss-animate": "^1.0.5",
+    "zod": "^3.21.4"
   },
   "devDependencies": {
     "@tanstack/eslint-plugin-query": "^4.29.9",
     "@types/bcrypt": "^5.0.0",
     "@types/node": "^20.2.5",
-    "@types/react": "^18.2.7",
+    "@types/react": "^18.2.8",
     "@types/react-dom": "^18.2.4",
     "autoprefixer": "10.4.14",
-    "eslint": "^8.41.0",
+    "eslint": "^8.42.0",
     "eslint-config-next": "^13.4.4",
     "postcss": "8.4.24",
     "prisma": "^4.15.0",
     "tailwindcss": "3.3.2",
-    "typescript": "^5.0.4"
+    "typescript": "^5.1.3"
   }
-}
+}
\ No newline at end of file
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index dbf586a..c9a096a 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -10,15 +10,53 @@ datasource db {
   url      = env("DATABASE_URL")
 }
 
+model Account {
+  id                       String   @id @default(cuid())
+  userId                   String   @unique
+  type                     String
+  provider                 String
+  providerAccountId        String
+  refresh_token            String?  @db.Text
+  access_token             String?  @db.Text
+  expires_at               Int?
+  token_type               String?
+  scope                    String?
+  id_token                 String?  @db.Text
+  session_state            String?
+  createdAt                DateTime @default(now()) @map(name: "created_at")
+  updatedAt                DateTime @default(now()) @map(name: "updated_at")
+  refresh_token_expires_in Int?
+
+  user User @relation(fields: [userId], references: [id], onDelete: Cascade)
+
+  @@unique([provider, providerAccountId])
+  @@map(name: "accounts")
+}
+
+model Session {
+  id           String   @id @default(cuid())
+  sessionToken String   @unique
+  userId       String   @unique
+  expires      DateTime
+
+  user User @relation(fields: [userId], references: [id], onDelete: Cascade)
+
+  @@map(name: "sessions")
+}
+
 model User {
-  id            Int       @id @default(autoincrement())
-  userName      String?   @unique 
-  name          String?   @default("u ${id}")
+  id            String    @id @default(cuid())
+  name          String?
+  username      String?   @unique @map("usernames")
   email         String?   @unique
-  password      String
   emailVerified DateTime?
-  image         String?   
-  createdAt     DateTime @default(now())
+  password      String?
+  image         String?
+  createdAt     DateTime  @default(now()) @map(name: "created_at")
+  updatedAt     DateTime  @default(now()) @map(name: "updated_at")
+
+  accounts Account[]
+  sessions Session[]
 
   Post    Post[]
   Comment Comment[]
@@ -26,48 +64,71 @@ model User {
 
   followers Follows[] @relation("follower")
   following Follows[] @relation("following")
+
+  @@map(name: "users")
+}
+
+model VerificationToken {
+  identifier String
+  token      String   @unique
+  expires    DateTime
+
+  @@unique([identifier, token])
+  @@map(name: "verification_tokens")
 }
 
 model Follows {
   follower    User     @relation("following", fields: [followerId], references: [id])
-  followerId  Int
+  followerId  String
   following   User     @relation("follower", fields: [followingId], references: [id])
-  followingId Int
+  followingId String
   createdAt   DateTime @default(now())
 
   @@id([followerId, followingId])
+  @@map(name: "follows")
 }
 
 model Post {
-  id        Int      @id @default(autoincrement())
-  createdAt DateTime @default(now())
-  updatedAt DateTime @updatedAt
+  id        String   @id @default(cuid())
+  createdAt DateTime @default(now()) @map(name: "created_at")
+  updatedAt DateTime @default(now()) @map(name: "updated_at")
+  userId    String
   content   String
   likeCount Int?     @default(0)
-  gameId    Int?
   published Boolean  @default(false)
-  userId    Int
 
   user    User      @relation(fields: [userId], references: [id], onDelete: Cascade)
   Comment Comment[]
   Like    Like[]
+
+  @@map(name: "posts")
 }
 
 model Like {
-  id     Int @id @default(autoincrement())
-  postId Int
-  userId Int
+  id        String  @id @default(cuid())
+  postId    String
+  commentId String?
+  userId    String
 
-  post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
-  user User @relation(fields: [userId], references: [id], onDelete: Cascade)
+  post    Post     @relation(fields: [postId], references: [id], onDelete: Cascade)
+  comment Comment? @relation(fields: [commentId], references: [id], onDelete: Cascade)
+  user    User     @relation(fields: [userId], references: [id], onDelete: Cascade)
+
+  @@map(name: "likes")
 }
 
 model Comment {
-  id        Int      @id @default(autoincrement())
+  id        String   @id @default(cuid())
+  createdAt DateTime @default(now()) @map(name: "created_at")
+  updatedAt DateTime @default(now()) @map(name: "updated_at")
   message   String
-  postId    Int
-  userId    Int
-  createdAt DateTime @default(now())
-  post      Post     @relation(fields: [postId], references: [id], onDelete: Cascade)
-  user      User     @relation(fields: [userId], references: [id], onDelete: Cascade)
+  likeCount Int?     @default(0)
+  postId    String
+  userId    String
+
+  post Post   @relation(fields: [postId], references: [id], onDelete: Cascade)
+  user User   @relation(fields: [userId], references: [id], onDelete: Cascade)
+  Like Like[]
+
+  @@map(name: "comments")
 }
diff --git a/tsconfig.json b/tsconfig.json
index a914549..88c4dba 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -27,7 +27,8 @@
       "@/*": [
         "./*"
       ]
-    }
+    },
+    "strictNullChecks": true
   },
   "include": [
     "next-env.d.ts",
diff --git a/types/next-auth.d.ts b/types/next-auth.d.ts
index 007c24c..19ff225 100644
--- a/types/next-auth.d.ts
+++ b/types/next-auth.d.ts
@@ -1,12 +1,18 @@
-import 'next-auth';
+import { User } from "next-auth"
+import { JWT } from "next-auth/jwt"
 
-declare module 'next-auth' {
+type UserId = string
+
+declare module "next-auth/jwt" {
+    interface JWT {
+        id: UserId
+    }
+}
+
+declare module "next-auth" {
     interface Session {
-        user: {
-            id: string;
-            name?: string | null;
-            email?: string | null;
-            image?: string | null;
-        };
+        user: User & {
+            id: UserId
+        }
     }
 }
\ No newline at end of file
-- 
GitLab