Skip to content

Commit d20f662

Browse files
CopilotCodeMan62
andauthored
Implement comprehensive authentication system with NextAuth.js (#4)
Co-authored-by: CodeMan62 <175127021+CodeMan62@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
1 parent c44ce07 commit d20f662

18 files changed

Lines changed: 1118 additions & 16 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import NextAuth from "next-auth"
2+
import { authOptions } from "@/lib/auth"
3+
4+
const handler = NextAuth(authOptions)
5+
6+
export { handler as GET, handler as POST }
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { NextRequest, NextResponse } from "next/server"
2+
// Use mock client temporarily until Prisma generation works
3+
import { PrismaClient } from "@collax/prisma/src/mock"
4+
5+
const prisma = new PrismaClient()
6+
7+
export async function POST(req: NextRequest) {
8+
try {
9+
const { email, password, name } = await req.json()
10+
11+
if (!email || !password || !name) {
12+
return NextResponse.json(
13+
{ message: "Missing required fields" },
14+
{ status: 400 }
15+
)
16+
}
17+
18+
// Check if user already exists
19+
const existingUser = await prisma.user.findUnique({
20+
where: { email }
21+
})
22+
23+
if (existingUser) {
24+
return NextResponse.json(
25+
{ message: "User already exists" },
26+
{ status: 400 }
27+
)
28+
}
29+
30+
// Create user (in production, hash the password)
31+
const user = await prisma.user.create({
32+
data: {
33+
email,
34+
name,
35+
// Note: In production, hash the password before storing
36+
// password: await bcrypt.hash(password, 12)
37+
}
38+
})
39+
40+
return NextResponse.json(
41+
{ message: "User created successfully", userId: user.id },
42+
{ status: 201 }
43+
)
44+
} catch (error) {
45+
console.error("Signup error:", error)
46+
return NextResponse.json(
47+
{ message: "Internal server error" },
48+
{ status: 500 }
49+
)
50+
}
51+
}

apps/web/app/auth/signin/page.tsx

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
"use client"
2+
3+
import { signIn } from "next-auth/react"
4+
import { Button } from "@collax/ui/components/ui/button"
5+
import { useState } from "react"
6+
7+
export default function SignIn() {
8+
const [email, setEmail] = useState("")
9+
const [password, setPassword] = useState("")
10+
11+
const handleCredentialsSignIn = async (e: React.FormEvent) => {
12+
e.preventDefault()
13+
await signIn("credentials", {
14+
email,
15+
password,
16+
callbackUrl: "/",
17+
})
18+
}
19+
20+
const handleGoogleSignIn = () => {
21+
signIn("google", { callbackUrl: "/" })
22+
}
23+
24+
return (
25+
<div className="min-h-screen flex items-center justify-center bg-gray-50">
26+
<div className="max-w-md w-full space-y-8">
27+
<div>
28+
<h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">
29+
Sign in to Collax
30+
</h2>
31+
<p className="mt-2 text-center text-sm text-gray-600">
32+
Your collaborative note-taking platform
33+
</p>
34+
</div>
35+
<div className="mt-8 space-y-6">
36+
<Button
37+
onClick={handleGoogleSignIn}
38+
className="w-full flex justify-center py-2 px-4"
39+
variant="outline"
40+
>
41+
Sign in with Google
42+
</Button>
43+
44+
<div className="relative">
45+
<div className="absolute inset-0 flex items-center">
46+
<div className="w-full border-t border-gray-300" />
47+
</div>
48+
<div className="relative flex justify-center text-sm">
49+
<span className="px-2 bg-gray-50 text-gray-500">Or continue with</span>
50+
</div>
51+
</div>
52+
53+
<form className="mt-8 space-y-6" onSubmit={handleCredentialsSignIn}>
54+
<div>
55+
<label htmlFor="email" className="sr-only">
56+
Email address
57+
</label>
58+
<input
59+
id="email"
60+
name="email"
61+
type="email"
62+
autoComplete="email"
63+
required
64+
className="relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
65+
placeholder="Email address"
66+
value={email}
67+
onChange={(e) => setEmail(e.target.value)}
68+
/>
69+
</div>
70+
<div>
71+
<label htmlFor="password" className="sr-only">
72+
Password
73+
</label>
74+
<input
75+
id="password"
76+
name="password"
77+
type="password"
78+
autoComplete="current-password"
79+
required
80+
className="relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
81+
placeholder="Password"
82+
value={password}
83+
onChange={(e) => setPassword(e.target.value)}
84+
/>
85+
</div>
86+
87+
<div>
88+
<Button
89+
type="submit"
90+
className="w-full flex justify-center py-2 px-4"
91+
>
92+
Sign in
93+
</Button>
94+
</div>
95+
</form>
96+
</div>
97+
</div>
98+
</div>
99+
)
100+
}

apps/web/app/auth/signup/page.tsx

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
"use client"
2+
3+
import { signIn } from "next-auth/react"
4+
import { Button } from "@collax/ui/components/ui/button"
5+
import { useState } from "react"
6+
import Link from "next/link"
7+
8+
export default function SignUp() {
9+
const [email, setEmail] = useState("")
10+
const [password, setPassword] = useState("")
11+
const [name, setName] = useState("")
12+
const [loading, setLoading] = useState(false)
13+
14+
const handleSignUp = async (e: React.FormEvent) => {
15+
e.preventDefault()
16+
setLoading(true)
17+
18+
try {
19+
const response = await fetch("/api/auth/signup", {
20+
method: "POST",
21+
headers: {
22+
"Content-Type": "application/json",
23+
},
24+
body: JSON.stringify({ email, password, name }),
25+
})
26+
27+
if (response.ok) {
28+
// Sign in after successful registration
29+
await signIn("credentials", {
30+
email,
31+
password,
32+
callbackUrl: "/",
33+
})
34+
} else {
35+
const data = await response.json()
36+
alert(data.message || "Something went wrong")
37+
}
38+
} catch {
39+
alert("Something went wrong")
40+
} finally {
41+
setLoading(false)
42+
}
43+
}
44+
45+
const handleGoogleSignIn = () => {
46+
signIn("google", { callbackUrl: "/" })
47+
}
48+
49+
return (
50+
<div className="min-h-screen flex items-center justify-center bg-gray-50">
51+
<div className="max-w-md w-full space-y-8">
52+
<div>
53+
<h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">
54+
Create your account
55+
</h2>
56+
<p className="mt-2 text-center text-sm text-gray-600">
57+
Join Collax and start collaborating
58+
</p>
59+
</div>
60+
<div className="mt-8 space-y-6">
61+
<Button
62+
onClick={handleGoogleSignIn}
63+
className="w-full flex justify-center py-2 px-4"
64+
variant="outline"
65+
>
66+
Sign up with Google
67+
</Button>
68+
69+
<div className="relative">
70+
<div className="absolute inset-0 flex items-center">
71+
<div className="w-full border-t border-gray-300" />
72+
</div>
73+
<div className="relative flex justify-center text-sm">
74+
<span className="px-2 bg-gray-50 text-gray-500">Or continue with</span>
75+
</div>
76+
</div>
77+
78+
<form className="mt-8 space-y-6" onSubmit={handleSignUp}>
79+
<div>
80+
<label htmlFor="name" className="sr-only">
81+
Full name
82+
</label>
83+
<input
84+
id="name"
85+
name="name"
86+
type="text"
87+
autoComplete="name"
88+
required
89+
className="relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
90+
placeholder="Full name"
91+
value={name}
92+
onChange={(e) => setName(e.target.value)}
93+
/>
94+
</div>
95+
<div>
96+
<label htmlFor="email" className="sr-only">
97+
Email address
98+
</label>
99+
<input
100+
id="email"
101+
name="email"
102+
type="email"
103+
autoComplete="email"
104+
required
105+
className="relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
106+
placeholder="Email address"
107+
value={email}
108+
onChange={(e) => setEmail(e.target.value)}
109+
/>
110+
</div>
111+
<div>
112+
<label htmlFor="password" className="sr-only">
113+
Password
114+
</label>
115+
<input
116+
id="password"
117+
name="password"
118+
type="password"
119+
autoComplete="new-password"
120+
required
121+
className="relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
122+
placeholder="Password"
123+
value={password}
124+
onChange={(e) => setPassword(e.target.value)}
125+
/>
126+
</div>
127+
128+
<div>
129+
<Button
130+
type="submit"
131+
className="w-full flex justify-center py-2 px-4"
132+
disabled={loading}
133+
>
134+
{loading ? "Creating account..." : "Sign up"}
135+
</Button>
136+
</div>
137+
138+
<div className="text-center">
139+
<span className="text-sm text-gray-600">
140+
Already have an account?{" "}
141+
<Link
142+
href="/auth/signin"
143+
className="font-medium text-indigo-600 hover:text-indigo-500"
144+
>
145+
Sign in
146+
</Link>
147+
</span>
148+
</div>
149+
</form>
150+
</div>
151+
</div>
152+
</div>
153+
)
154+
}

apps/web/app/dashboard/page.tsx

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
"use client"
2+
3+
import { useSession } from "next-auth/react"
4+
import AuthNav from "@/components/auth-nav"
5+
import { Button } from "@collax/ui/components/ui/button"
6+
7+
import Link from "next/link"
8+
9+
export default function Dashboard() {
10+
const { data: session } = useSession()
11+
12+
return (
13+
<div className="min-h-screen bg-gray-50">
14+
{/* Navigation Header */}
15+
<nav className="bg-white shadow-sm border-b">
16+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
17+
<div className="flex justify-between h-16">
18+
<div className="flex items-center">
19+
<Link href="/" className="text-xl font-bold text-gray-900">Collax</Link>
20+
</div>
21+
<div className="flex items-center">
22+
<AuthNav />
23+
</div>
24+
</div>
25+
</div>
26+
</nav>
27+
28+
{/* Main Content */}
29+
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
30+
<div className="mb-8">
31+
<h1 className="text-3xl font-bold text-gray-900">Dashboard</h1>
32+
<p className="text-gray-600">Welcome back, {session?.user?.name || session?.user?.email}!</p>
33+
</div>
34+
35+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
36+
<div className="bg-white rounded-lg shadow p-6">
37+
<h2 className="text-xl font-semibold text-gray-800 mb-4">My Notes</h2>
38+
<p className="text-gray-600 mb-4">Manage your personal notes</p>
39+
<Button className="w-full">View Notes</Button>
40+
</div>
41+
42+
<div className="bg-white rounded-lg shadow p-6">
43+
<h2 className="text-xl font-semibold text-gray-800 mb-4">Shared Notes</h2>
44+
<p className="text-gray-600 mb-4">Collaborate with others</p>
45+
<Button className="w-full" variant="outline">View Shared</Button>
46+
</div>
47+
48+
<div className="bg-white rounded-lg shadow p-6">
49+
<h2 className="text-xl font-semibold text-gray-800 mb-4">Create New</h2>
50+
<p className="text-gray-600 mb-4">Start a new note</p>
51+
<Button className="w-full" variant="secondary">New Note</Button>
52+
</div>
53+
</div>
54+
55+
<div className="mt-8 bg-white rounded-lg shadow p-6">
56+
<h2 className="text-xl font-semibold text-gray-800 mb-4">Recent Activity</h2>
57+
<p className="text-gray-600">Your recent notes and collaborations will appear here.</p>
58+
</div>
59+
</main>
60+
</div>
61+
)
62+
}

0 commit comments

Comments
 (0)