diff --git a/app/(auth)/dashboard/page.tsx b/app/(auth)/dashboard/page.tsx
new file mode 100644
index 0000000..b8e1a83
--- /dev/null
+++ b/app/(auth)/dashboard/page.tsx
@@ -0,0 +1,7 @@
+import React from 'react'
+
+export default function DashboardPage() {
+ return (
+
DashboardPage
+ )
+}
diff --git a/app/events/[eventId]/guests/page.tsx b/app/(auth)/events/[eventId]/guests/page.tsx
similarity index 100%
rename from app/events/[eventId]/guests/page.tsx
rename to app/(auth)/events/[eventId]/guests/page.tsx
diff --git a/app/events/create/page.tsx b/app/(auth)/events/create/page.tsx
similarity index 100%
rename from app/events/create/page.tsx
rename to app/(auth)/events/create/page.tsx
diff --git a/app/(auth)/layout.tsx b/app/(auth)/layout.tsx
new file mode 100644
index 0000000..60035d8
--- /dev/null
+++ b/app/(auth)/layout.tsx
@@ -0,0 +1,21 @@
+'use client'
+
+import { SessionProvider, useSession } from 'next-auth/react'
+import { redirect } from 'next/navigation'
+import { ReactNode } from 'react'
+import Navbar from '@/components/Navbar'
+
+export default function AuthLayout({ children }: { children: ReactNode }) {
+
+ return (
+ <>
+
+
+
+ {/* Could also add a private header here */}
+ {children}
+
+
+ >
+ )
+}
diff --git a/app/(public)/layout.tsx b/app/(public)/layout.tsx
new file mode 100644
index 0000000..a4a066a
--- /dev/null
+++ b/app/(public)/layout.tsx
@@ -0,0 +1,10 @@
+import { ReactNode } from 'react'
+
+export default function PublicLayout({ children }: { children: ReactNode }) {
+ return (
+
+ {/* Public site header if any */}
+ {children}
+
+ )
+}
\ No newline at end of file
diff --git a/app/(public)/login/page.tsx b/app/(public)/login/page.tsx
new file mode 100644
index 0000000..d3cb67b
--- /dev/null
+++ b/app/(public)/login/page.tsx
@@ -0,0 +1,10 @@
+import LoginForm from '@/components/LoginForm';
+import React, { useState } from 'react'
+
+export default function LoginPage() {
+ return (
+
+
+
+ )
+}
diff --git a/app/page.tsx b/app/(public)/page.tsx
similarity index 100%
rename from app/page.tsx
rename to app/(public)/page.tsx
diff --git a/app/setup/page.tsx b/app/(public)/setup/page.tsx
similarity index 100%
rename from app/setup/page.tsx
rename to app/(public)/setup/page.tsx
diff --git a/app/api/auth/[...nextauth]/route.ts b/app/api/auth/[...nextauth]/route.ts
new file mode 100644
index 0000000..f58d6e0
--- /dev/null
+++ b/app/api/auth/[...nextauth]/route.ts
@@ -0,0 +1,76 @@
+import NextAuth, { type NextAuthOptions } from 'next-auth'
+import CredentialsProvider from 'next-auth/providers/credentials'
+import { PrismaClient } from '@prisma/client'
+import bcrypt from 'bcrypt'
+
+const prisma = new PrismaClient()
+
+export const authOptions: NextAuthOptions = {
+ providers: [
+ CredentialsProvider({
+ name: 'Credentials',
+ credentials: {
+ email: { label: 'Email', type: 'email' },
+ password: { label: 'Password', type: 'password' },
+ },
+ async authorize(credentials) {
+ if (!credentials?.email || !credentials?.password) {
+ console.log('[AUTH] Missing credentials')
+ return null
+ }
+
+ const user = await prisma.user.findUnique({
+ where: { email: credentials.email },
+ })
+
+ if (!user) {
+ console.log('[AUTH] User not found')
+ return null
+ }
+
+ if (!user.password) {
+ console.log('[AUTH] User has no password set')
+ return null
+ }
+
+ const isValid = await bcrypt.compare(credentials.password, user.password)
+ if (!isValid) {
+ console.log('[AUTH] Invalid password')
+ return null
+ }
+
+ console.log('[AUTH] Successful login', user.email)
+
+ return {
+ id: user.id,
+ email: user.email,
+ name: user.name,
+ role: user.role,
+ }
+ },
+ }),
+ ],
+ session: {
+ strategy: 'jwt',
+ },
+ callbacks: {
+ async jwt({ token, user }) {
+ if (user) {
+ token.id = user.id
+ token.role = user.role
+ }
+ return token
+ },
+ async session({ session, token }) {
+ if (session.user) {
+ session.user.id = token.id as string
+ session.user.role = token.role as "COUPLE" | "PLANNER" | "GUEST"
+ }
+ return session
+ },
+ },
+ secret: process.env.NEXTAUTH_SECRET,
+}
+
+const handler = NextAuth(authOptions)
+export { handler as GET, handler as POST }
diff --git a/components/LoginForm.tsx b/components/LoginForm.tsx
new file mode 100644
index 0000000..9393e71
--- /dev/null
+++ b/components/LoginForm.tsx
@@ -0,0 +1,53 @@
+'use client'
+
+import { signIn } from 'next-auth/react';
+import { useRouter } from 'next/navigation';
+import React, { useState } from 'react'
+
+export default function LoginForm() {
+ const [email, setEmail] = useState('');
+ const [password, setPassword] = useState('');
+ const [error, setError] = useState('');
+ const router = useRouter();
+
+ async function handleSubmit(e: React.FormEvent) {
+ e.preventDefault();
+
+ const result = await signIn('credentials', {
+ redirect: false,
+ email,
+ password,
+ })
+
+ console.log('[CLIENT] signIn result:', result)
+
+ if (result?.error) {
+ setError(result.error)
+ } else {
+ router.push('/')
+ }
+ }
+ return (
+
+ )
+}
diff --git a/components/Navbar.tsx b/components/Navbar.tsx
new file mode 100644
index 0000000..27a5add
--- /dev/null
+++ b/components/Navbar.tsx
@@ -0,0 +1,27 @@
+'use client'
+
+import { useSession, signOut } from "next-auth/react";
+
+export default function Navbar() {
+ const { data: session, status } = useSession();
+
+ if (status === 'loading') return null;
+ if (!session?.user) return null
+
+ return (
+
+ )
+}
diff --git a/lib/auth.ts b/lib/auth.ts
deleted file mode 100644
index 88b984c..0000000
--- a/lib/auth.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-// lib/auth.ts
-import NextAuth from 'next-auth';
-import { PrismaAdapter } from '@next-auth/prisma-adapter';
-import CredentialsProvider from 'next-auth/providers/credentials';
-import { prisma } from './prisma';
-import type { NextAuthOptions, User } from 'next-auth';
-import bcrypt from "bcrypt";
-
-export const authOptions: NextAuthOptions = {
- adapter: PrismaAdapter(prisma),
- providers: [
- CredentialsProvider({
- name: 'Credentials',
- credentials: {
- email: { label: "Email", type: "email" },
- password: { label: "Password", type: "password" },
- },
- async authorize(credentials) {
- if (!credentials?.email || !credentials?.password) {
- throw new Error("Missing email or password");
- }
-
- const user = await prisma.user.findUnique({
- where: { email: credentials.email },
- });
-
- if (!user || !user.password) {
- throw new Error("Invalid credentials");
- }
-
- const isValid = await bcrypt.compare(credentials.password, user.password);
- if (!isValid) {
- throw new Error("Invalid credentials");
- }
-
- return {
- id: user.id,
- email: user.email,
- name: user.name,
- role: user.role,
- };
- },
- }),
- ],
- session: {
- strategy: 'jwt',
- },
- callbacks: {
- async session({ session, token }: { session: any; token: any }) {
- if (session.user) {
- session.user.id = token.sub!;
- session.user.role = token.role;
- }
- return session;
- },
- async jwt({ token, user }: { token: any; user?: any }) {
- if (user) {
- token.role = user.role;
- }
- return token;
- },
- },
- secret: process.env.NEXTAUTH_SECRET,
-};
-
-export const {
- handlers: { GET, POST },
- auth,
-} = NextAuth(authOptions);
diff --git a/types/next-auth.d.ts b/types/next-auth.d.ts
index 435ed8c..618203b 100644
--- a/types/next-auth.d.ts
+++ b/types/next-auth.d.ts
@@ -1,20 +1,21 @@
-import NextAuth from "next-auth";
+import NextAuth from "next-auth"
declare module "next-auth" {
- interface User {
- id: string;
- role: "COUPLE" | "PLANNER" | "GUEST";
- }
-
interface Session {
user: {
- id: string;
- email: string;
- role: "COUPLE" | "PLANNER" | "GUEST";
- };
+ id: string
+ email: string
+ role: "COUPLE" | "PLANNER" | "GUEST"
+ }
+ }
+
+ interface User {
+ id: string
+ role: "COUPLE" | "PLANNER" | "GUEST"
}
interface JWT {
- role: "COUPLE" | "PLANNER" | "GUEST";
+ id: string
+ role: "COUPLE" | "PLANNER" | "GUEST"
}
}