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

Merge branch 'feat.AuthFixes' into 'main'

Feat.auth fixes

See merge request !19
parents 6c59f1e2 724a3af4
No related branches found
No related tags found
1 merge request!19Feat.auth fixes
Pipeline #36365 passed
Showing
with 232 additions and 346 deletions
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>
......
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>
)
......
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"
}
......
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
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 }
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
},
......
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"
}
......
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
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
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
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
......@@ -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>
......
'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
'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 { useState } from 'react'
export const SignupForm = () => {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [error, setError] = useState<string | null>(null)
const onSubmit = async (e: React.FormEvent) => {
e.preventDefault()
try {
const res = await fetch('/api/signup', {
method: 'POST',
body: JSON.stringify({
email,
password
}),
headers: {
'Content-Type': 'application/json'
}
})
if (res.ok) {
signIn()
} else {
setError((await res.json()).error)
}
} catch (error: any) {
setError(error?.message)
}
}
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">
Sign up
</Button>
</div>
</form>
)
}
\ No newline at end of file
......@@ -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,
......
import { Icons } from "./icons";
export function GameUnityLogo({ className }: { className?: string }) {
return (
<>
<Icons.logo className={`dark:hidden ${className}`} />
<Icons.logoWhite className={`hidden dark:block ${className}`} />
</>
)
}
......@@ -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
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()
......
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