From fcbb8ac8042afd32a02a20cb2042ba7730da3ebb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20Akg=C3=BCl?= <s86116@bht-berlin.de> Date: Tue, 30 May 2023 17:23:09 +0200 Subject: [PATCH] nextauth mid --- app/(auth)/layout.tsx | 7 +++ app/(auth)/login/page.tsx | 20 +++++++-- app/(auth)/signup/page.tsx | 20 +++++++-- app/(content)/(home)/home/page.tsx | 12 ++--- app/api/likes/likeService.ts | 8 ++-- app/api/messages/route.ts | 6 +-- app/api/signup/route.ts | 32 ++++++++++++++ components/auth-login-form.tsx | 69 +++++++++++++++++++++++++++++ components/auth-signup-form.tsx | 71 ++++++++++++++++++++++++++++++ components/like-button.tsx | 2 +- components/nav.tsx | 2 +- components/post-messages.tsx | 10 ++--- components/ui/alert.tsx | 59 +++++++++++++++++++++++++ components/ui/label.tsx | 26 +++++++++++ lib/validations/auth.ts | 5 +++ package-lock.json | 24 ++++++++++ package.json | 1 + prisma/schema.prisma | 2 +- 18 files changed, 347 insertions(+), 29 deletions(-) create mode 100644 app/(auth)/layout.tsx create mode 100644 app/api/signup/route.ts create mode 100644 components/auth-login-form.tsx create mode 100644 components/auth-signup-form.tsx create mode 100644 components/ui/alert.tsx create mode 100644 components/ui/label.tsx create mode 100644 lib/validations/auth.ts diff --git a/app/(auth)/layout.tsx b/app/(auth)/layout.tsx new file mode 100644 index 0000000..0b6cf1f --- /dev/null +++ b/app/(auth)/layout.tsx @@ -0,0 +1,7 @@ +interface AuthLayoutProps { + children: React.ReactNode +} + +export default function AuthLayout({ children }: AuthLayoutProps) { + return <div className="min-h-screen">{children}</div> +} \ No newline at end of file diff --git a/app/(auth)/login/page.tsx b/app/(auth)/login/page.tsx index 126f637..f3ceae7 100644 --- a/app/(auth)/login/page.tsx +++ b/app/(auth)/login/page.tsx @@ -1,7 +1,19 @@ -export default function Login() { +import { LoginForm } from '@/components/auth-login-form' +import Link from 'next/link' + +export default function LoginPage() { return ( - <div> - <h1>Login Page WIP</h1> + <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-white 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>{' '} + </p> + </div> </div> - ); + ) } \ No newline at end of file diff --git a/app/(auth)/signup/page.tsx b/app/(auth)/signup/page.tsx index 8477ded..8f2d6d9 100644 --- a/app/(auth)/signup/page.tsx +++ b/app/(auth)/signup/page.tsx @@ -1,7 +1,19 @@ -export default function Signup() { +import { SignupForm } from '@/components/auth-signup-form' +import Link from 'next/link' + +export default function SignupPage() { return ( - <div> - <h1>Signup Page WIP</h1> + <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-white 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> </div> - ); + ) } \ No newline at end of file diff --git a/app/(content)/(home)/home/page.tsx b/app/(content)/(home)/home/page.tsx index 9a41b73..836569d 100644 --- a/app/(content)/(home)/home/page.tsx +++ b/app/(content)/(home)/home/page.tsx @@ -3,7 +3,7 @@ import PostMessageForm from "@/components/post-messages"; import { prisma } from "@/lib/db"; import { Prisma } from "@prisma/client"; -type messageType = Prisma.MessageUncheckedCreateInput +type messageType = Prisma.PostUncheckedCreateInput type messageItemProps = { msg: messageType; }; @@ -11,9 +11,9 @@ type messageItemProps = { export default async function HomePage() { let messages = null try { - messages = await prisma.message.findMany({ + messages = await prisma.post.findMany({ orderBy: { - sentAt: "desc" + createdAt: "desc" } }) @@ -52,9 +52,9 @@ const MessageItem = ({ msg }: messageItemProps) => { <div className="ml-4 flex flex-col"> <div> <div className="flex items-center"> - <span className="font-bold mr-2">{msg.author}</span> + <span className="font-bold mr-2">{msg.userId}</span> <span className="text-gray-500 text-sm"> - {formatDate(new Date(msg.sentAt!))} + {formatDate(new Date(msg.createdAt!))} </span> </div> <div className="text-gray-800">{msg.content}</div> @@ -68,7 +68,7 @@ const MessageItem = ({ msg }: messageItemProps) => { </div> <LikeButton data={{ postId: msg.id, - author: msg.author + userId: msg.userId }} /> <span className="text-gray-600">Like Count: {msg.likeCount} | <span className="text-gray-600">ReplyButton (Number of Replies)</span></span> </div> diff --git a/app/api/likes/likeService.ts b/app/api/likes/likeService.ts index 80fe5f3..0dd748e 100644 --- a/app/api/likes/likeService.ts +++ b/app/api/likes/likeService.ts @@ -16,7 +16,7 @@ export async function putLike(like: likeType): Promise<likeType | undefined> { where: { id: like.id, postId: like.postId, - author: like.author + userId: like.userId } }) @@ -31,7 +31,7 @@ export async function putLike(like: likeType): Promise<likeType | undefined> { } }) - const msg = await prisma.message.update({ + const msg = await prisma.post.update({ where: { id: like.postId }, @@ -46,11 +46,11 @@ export async function putLike(like: likeType): Promise<likeType | undefined> { const createdLike = await prisma.like.create({ data: { postId: like.postId, - author: like.author + userId: like.userId } }) - const updatedMessage = await prisma.message.update({ + const updatedMessage = await prisma.post.update({ where: { id: like.postId }, diff --git a/app/api/messages/route.ts b/app/api/messages/route.ts index 20a1b72..f2799ef 100644 --- a/app/api/messages/route.ts +++ b/app/api/messages/route.ts @@ -7,7 +7,7 @@ export async function POST(req: NextRequest) { console.log(data) try { - await prisma.message.create({ + await prisma.post.create({ data: data }) @@ -27,9 +27,9 @@ export async function GET(req: NextRequest, res: NextResponse) { console.log(data) try { - const messages = await prisma.message.findMany({ + const messages = await prisma.post.findMany({ orderBy: { - sentAt: "desc" + createdAt: "desc" } }) diff --git a/app/api/signup/route.ts b/app/api/signup/route.ts new file mode 100644 index 0000000..79937ac --- /dev/null +++ b/app/api/signup/route.ts @@ -0,0 +1,32 @@ +import { prisma } 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 hashed = await hash(password, 12) + + const user = await prisma.user.create({ + data: { + email, + password: hashed + } + }) + + return NextResponse.json({ + user: { + email: user.email + } + }) + } catch (err: any) { + return new NextResponse( + JSON.stringify({ + error: err.message + }), + { + status: 500 + } + ) + } +} \ No newline at end of file diff --git a/components/auth-login-form.tsx b/components/auth-login-form.tsx new file mode 100644 index 0000000..f932027 --- /dev/null +++ b/components/auth-login-form.tsx @@ -0,0 +1,69 @@ +'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') || '/dashboard' + 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 new file mode 100644 index 0000000..4eb7e63 --- /dev/null +++ b/components/auth-signup-form.tsx @@ -0,0 +1,71 @@ +'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 diff --git a/components/like-button.tsx b/components/like-button.tsx index 1cbe755..6a08934 100644 --- a/components/like-button.tsx +++ b/components/like-button.tsx @@ -15,7 +15,7 @@ export default function LikeButton(props: { data: likeType }) { e.preventDefault() const msgLikeData = props.data; const likeData = {} as likeType - likeData.author = msgLikeData.author + likeData.userId = msgLikeData.userId likeData.postId = msgLikeData.postId const response = await fetch('http://localhost:3000/api/likes', { diff --git a/components/nav.tsx b/components/nav.tsx index 7be55e1..fbf7334 100644 --- a/components/nav.tsx +++ b/components/nav.tsx @@ -58,7 +58,7 @@ export default function DashboardNav({ items }: DashboardNavProps) { <Link href="/login" className={cn(buttonVariants({ size: "lg" }))}>Log In</Link> <Link href="/signup" className={cn(buttonVariants({ size: "lg", variant: "outline" }))}>Sign Up</Link> <p> - Unlock endless possibilities - register or log in to unleash the full potential of our website. + Unlock endless possibilities - sign up or log in to unleash the full potential of our website. </p> </div> } diff --git a/components/post-messages.tsx b/components/post-messages.tsx index e17f95f..a6d9d14 100644 --- a/components/post-messages.tsx +++ b/components/post-messages.tsx @@ -1,12 +1,12 @@ "use client" -import { Message, Prisma } from "@prisma/client"; +import { Post, Prisma } from "@prisma/client"; import { useRouter } from "next/navigation"; import { startTransition, useState } from "react"; -type messageType = Prisma.MessageUncheckedCreateInput +type messageType = Prisma.PostUncheckedCreateInput -export default function PostMessageForm(props: { data: Message[] | null }) { +export default function PostMessageForm(props: { data: Post[] | null }) { const [formData, setFormData] = useState<messageType>({ content: "" } as messageType); // const [messagesState, setMessages] = useState(props.data) @@ -16,7 +16,7 @@ export default function PostMessageForm(props: { data: Message[] | null }) { e.preventDefault() // setMessages([...messagesState, formData]) console.log(formData) - formData.author = "Default Author" + formData.userId = "Default Author" const response = await fetch('http://localhost:3000/api/messages', { method: 'POST', body: JSON.stringify(formData) @@ -34,7 +34,7 @@ export default function PostMessageForm(props: { data: Message[] | null }) { const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => { const { value } = e.target; - setFormData({ content: value }); + setFormData({ ...formData, content: value }); }; return ( diff --git a/components/ui/alert.tsx b/components/ui/alert.tsx new file mode 100644 index 0000000..9ddbc46 --- /dev/null +++ b/components/ui/alert.tsx @@ -0,0 +1,59 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const alertVariants = cva( + "relative w-full rounded-lg border p-4 [&>svg]:absolute [&>svg]:text-foreground [&>svg]:left-4 [&>svg]:top-4 [&>svg+div]:translate-y-[-3px] [&:has(svg)]:pl-11", + { + variants: { + variant: { + default: "bg-background text-foreground", + destructive: + "text-destructive border-destructive/50 dark:border-destructive [&>svg]:text-destructive text-destructive", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +const Alert = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants> +>(({ className, variant, ...props }, ref) => ( + <div + ref={ref} + role="alert" + className={cn(alertVariants({ variant }), className)} + {...props} + /> +)) +Alert.displayName = "Alert" + +const AlertTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes<HTMLHeadingElement> +>(({ className, ...props }, ref) => ( + <h5 + ref={ref} + className={cn("mb-1 font-medium leading-none tracking-tight", className)} + {...props} + /> +)) +AlertTitle.displayName = "AlertTitle" + +const AlertDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes<HTMLParagraphElement> +>(({ className, ...props }, ref) => ( + <div + ref={ref} + className={cn("text-sm [&_p]:leading-relaxed", className)} + {...props} + /> +)) +AlertDescription.displayName = "AlertDescription" + +export { Alert, AlertTitle, AlertDescription } diff --git a/components/ui/label.tsx b/components/ui/label.tsx new file mode 100644 index 0000000..5341821 --- /dev/null +++ b/components/ui/label.tsx @@ -0,0 +1,26 @@ +"use client" + +import * as React from "react" +import * as LabelPrimitive from "@radix-ui/react-label" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const labelVariants = cva( + "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" +) + +const Label = React.forwardRef< + React.ElementRef<typeof LabelPrimitive.Root>, + React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & + VariantProps<typeof labelVariants> +>(({ className, ...props }, ref) => ( + <LabelPrimitive.Root + ref={ref} + className={cn(labelVariants(), className)} + {...props} + /> +)) +Label.displayName = LabelPrimitive.Root.displayName + +export { Label } diff --git a/lib/validations/auth.ts b/lib/validations/auth.ts new file mode 100644 index 0000000..e65b5cb --- /dev/null +++ b/lib/validations/auth.ts @@ -0,0 +1,5 @@ +import * as z from "zod" + +export const userAuthSchema = z.object({ + email: z.string().email(), +}) \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index bfb1b94..6b5a068 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@prisma/client": "^4.14.1", "@radix-ui/react-dropdown-menu": "^2.0.5", + "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-scroll-area": "^1.0.4", "@radix-ui/react-select": "^1.2.2", "@radix-ui/react-slot": "^1.0.2", @@ -719,6 +720,29 @@ } } }, + "node_modules/@radix-ui/react-label": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.0.2.tgz", + "integrity": "sha512-N5ehvlM7qoTLx7nWPodsPYPgMzA5WM8zZChQg8nyFJKnDO5WHdba1vv5/H6IO5LtJMfD2Q3wh1qHFGNtK0w3bQ==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-primitive": "1.0.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-menu": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.0.5.tgz", diff --git a/package.json b/package.json index 0a8e66a..f269b67 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "dependencies": { "@prisma/client": "^4.14.1", "@radix-ui/react-dropdown-menu": "^2.0.5", + "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-scroll-area": "^1.0.4", "@radix-ui/react-select": "^1.2.2", "@radix-ui/react-slot": "^1.0.2", diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 6dc5c1e..19feee8 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -12,7 +12,7 @@ datasource db { model User { id String @id @default(dbgenerated()) @db.Uuid - userName String @unique + userName String? @unique name String? email String? @unique password String -- GitLab