From f4ae4f3218f31186bd48d09564a4335305dbbd57 Mon Sep 17 00:00:00 2001 From: MartinBraquet Date: Mon, 28 Jul 2025 20:46:15 +0200 Subject: [PATCH] Add profile description and gender --- README.md | 9 ++ app/api/auth/[...nextauth]/route.ts | 88 +--------------- app/api/user/update-profile/route.ts | 50 +++++++++ app/complete-profile/page.tsx | 151 +++++++++++++++++++++++++++ app/profiles/[id]/page.tsx | 15 +-- app/profiles/page.tsx | 3 +- app/register/page.tsx | 6 +- lib/auth.ts | 89 ++++++++++++++++ prisma/schema.prisma | 7 +- prisma/seed.ts | 1 + 10 files changed, 317 insertions(+), 102 deletions(-) create mode 100644 app/api/user/update-profile/route.ts create mode 100644 app/complete-profile/page.tsx create mode 100644 lib/auth.ts diff --git a/README.md b/README.md index e5b62b5d..b917f6e6 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,15 @@ To contribute, please submit a pull request or issue, or fill out this [form](ht - [ ] Search through all the profile variables - [ ] (Set up chat / direct messaging) +#### Secondary To Do + +Any action item is open to anyone for collaboration, but the following ones are particularly easy to do for first-time contributors. + +- [ ] Clean up terms and conditions +- [ ] Clean up privacy notice +- [ ] Clean up learn more page +- [ ] Add dark theme + ## Implementation The web app is coded in Typescript using React as front-end and prisma as back-end. It includes: diff --git a/app/api/auth/[...nextauth]/route.ts b/app/api/auth/[...nextauth]/route.ts index 37d7347a..47105110 100644 --- a/app/api/auth/[...nextauth]/route.ts +++ b/app/api/auth/[...nextauth]/route.ts @@ -1,89 +1,5 @@ -import NextAuth, {type NextAuthOptions} from "next-auth"; -import GoogleProvider from "next-auth/providers/google"; -import CredentialsProvider from "next-auth/providers/credentials"; -import {PrismaAdapter} from "@auth/prisma-adapter"; -import {prisma} from "@/lib/prisma"; -import bcrypt from "bcryptjs"; - -export const authOptions: NextAuthOptions = { - adapter: PrismaAdapter(prisma), - session: { - strategy: "jwt", - }, - providers: [ - GoogleProvider({ - clientId: process.env.GOOGLE_CLIENT_ID!, - clientSecret: process.env.GOOGLE_CLIENT_SECRET!, - }), - 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("Email and password are required"); - } - - const user = await prisma.user.findUnique({ - where: {email: credentials.email}, - }); - - if (!user || !user.password) { - throw new Error("Invalid email or password"); - } - - const isCorrectPassword = await bcrypt.compare( - credentials.password, - user.password - ); - - if (!isCorrectPassword) { - throw new Error("Invalid email or password"); - } - - return { - id: user.id, - email: user.email, - name: user.name, - image: user.image, - }; - }, - }), - ], - pages: { - signIn: "/login", - error: "/login", - }, - callbacks: { - async jwt({token, user}) { - if (user) { - token.id = user.id; - token.email = user.email; - token.name = user.name; - token.picture = user.image; - } - return token; - }, - async session({session, token}) { - if (token && session.user) { - session.user.id = token.id as string; - session.user.name = token.name as string; - session.user.email = token.email as string; - session.user.image = token.picture as string; - } - return session; - }, - async redirect({url, baseUrl}) { - if (url.startsWith("/")) return `${baseUrl}${url}`; - else if (new URL(url).origin === baseUrl) return url; - return baseUrl; - }, - }, - secret: process.env.NEXTAUTH_SECRET, - debug: process.env.NODE_ENV === "development", -} satisfies NextAuthOptions; +import NextAuth from "next-auth"; +import {authOptions} from "@/lib/auth"; const authHandler = NextAuth(authOptions); export {authHandler as GET, authHandler as POST}; diff --git a/app/api/user/update-profile/route.ts b/app/api/user/update-profile/route.ts new file mode 100644 index 00000000..c695f4ea --- /dev/null +++ b/app/api/user/update-profile/route.ts @@ -0,0 +1,50 @@ +import {NextResponse} from "next/server"; +import {prisma} from "@/lib/prisma"; +import {getSession} from "@/lib/auth"; + +export async function POST(req: Request) { + try { + const session = await getSession(); + + if (!session?.user?.email) { + return NextResponse.json( + {error: "Not authenticated"}, + {status: 401} + ); + } + + const {description, gender} = await req.json(); + + // Validate required fields + if (!gender) { + return NextResponse.json( + {error: "Gender is required"}, + {status: 400} + ); + } + + // Update user with the new profile information + const updatedUser = await prisma.user.update({ + where: {email: session.user.email}, + data: { + description: description || null, + gender: gender || null, + }, + select: { + id: true, + email: true, + name: true, + description: true, + gender: true, + }, + }); + + return NextResponse.json(updatedUser); + } catch (error) { + console.error('Profile update error:', error); + return NextResponse.json( + {error: "Failed to update profile"}, + {status: 500} + ); + } +} diff --git a/app/complete-profile/page.tsx b/app/complete-profile/page.tsx new file mode 100644 index 00000000..ca9f23e4 --- /dev/null +++ b/app/complete-profile/page.tsx @@ -0,0 +1,151 @@ +'use client'; + +import { useState } from 'react'; +import { useRouter } from 'next/navigation'; +import { useSession } from 'next-auth/react'; + +export default function CompleteProfile() { + const [description, setDescription] = useState(''); + const [gender, setGender] = useState(''); + const [isSubmitting, setIsSubmitting] = useState(false); + const [error, setError] = useState(''); + const router = useRouter(); + const { data: session, update } = useSession(); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + if (!gender) { + setError('Please select your gender'); + return; + } + + try { + setIsSubmitting(true); + setError(''); + + const response = await fetch('/api/user/update-profile', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + description, + gender, + }), + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.error || 'Failed to update profile'); + } + + // Update the session to reflect the changes + await update(); + + // Redirect to the home page or dashboard + router.push('/'); + } catch (error) { + console.error('Profile update error:', error); + setError(error instanceof Error ? error.message : 'Failed to update profile'); + } finally { + setIsSubmitting(false); + } + }; + + return ( +
+
+
+

+ Complete Your Profile +

+ {/*

*/} + {/* Help us know you better (this information can be updated later)*/} + {/*

*/} +
+ + {error && ( +
+
+
+ + + +
+
+

{error}

+
+
+
+ )} + +
+
+
+ + +
+ +
+ +