diff --git a/app/api/profile/update/route.ts b/app/api/profile/update/route.ts new file mode 100644 index 0000000..5b41d63 --- /dev/null +++ b/app/api/profile/update/route.ts @@ -0,0 +1,72 @@ +import { NextRequest, NextResponse } from "next/server"; +import { getServerSession } from "next-auth/next"; +import { revalidatePath } from "next/cache"; +import pool from "@/backend/config/db"; + +export async function POST(request: NextRequest) { + try { + const session = await getServerSession(); + + if (!session || !session.user) { + return NextResponse.json( + { error: "Unauthorized" }, + { status: 401 } + ); + } + + const body = await request.json(); + const { name, phone, address, city, country } = body; + const userEmail = session.user.email; + + // Validate phone number: only digits and max 11 characters + if (phone && !/^\d{0,11}$/.test(phone)) { + return NextResponse.json( + { error: "Phone number must contain only digits" }, + { status: 400 } + ); + } + + // Update user in database + const query = ` + UPDATE users + SET name = $2, + updated_at = CURRENT_TIMESTAMP + WHERE email = $1 + RETURNING user_id, email, name, picture, created_at, updated_at + `; + + const result = await pool.query(query, [userEmail, name]); + + if (result.rows.length === 0) { + return NextResponse.json( + { error: "User not found" }, + { status: 404 } + ); + } + + const updatedUser = result.rows[0]; + console.log("✅ User updated in database:", updatedUser); + + // Revalidate the profile page to clear cache + revalidatePath("/profile"); + revalidatePath("/profile/edit"); + + return NextResponse.json( + { + message: "Profile updated successfully", + data: { + name: updatedUser.name, + email: updatedUser.email, + id: updatedUser.user_id + } + }, + { status: 200 } + ); + } catch (error) { + console.error("❌ Profile update error:", error); + return NextResponse.json( + { error: error instanceof Error ? error.message : "Failed to update profile" }, + { status: 500 } + ); + } +} diff --git a/app/profile/edit/page.tsx b/app/profile/edit/page.tsx new file mode 100644 index 0000000..6a135e1 --- /dev/null +++ b/app/profile/edit/page.tsx @@ -0,0 +1,274 @@ +"use client"; + +import { useSession } from "next-auth/react"; +import { useRouter } from "next/navigation"; +import { useEffect, useState } from "react"; +import { User, Mail, Phone, MapPin, ChevronLeft } from "lucide-react"; +import SidebarLayout from "@/Components/SidebarLayout"; +import Link from "next/link"; +import toast from "react-hot-toast"; + +const EditProfilePage = () => { + const { data: session, status } = useSession(); + const router = useRouter(); + const [loading, setLoading] = useState(false); + const [formData, setFormData] = useState({ + name: "", + email: "", + phone: "", + address: "", + city: "", + country: "", + }); + + useEffect(() => { + if (status === "unauthenticated") { + router.push("/login?callbackUrl=/profile/edit"); + return; + } + + if (session?.user) { + setFormData({ + name: session.user.name || "", + email: session.user.email || "", + phone: "", + address: "", + city: "", + country: "", + }); + } + }, [session, status, router]); + + const handleChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + + // For phone field: only allow digits and max 11 characters + if (name === "phone") { + const digitsOnly = value.replace(/\D/g, "").slice(0, 11); + setFormData((prev) => ({ + ...prev, + [name]: digitsOnly, + })); + return; + } + + // Capitalize first letter of each word for name and other text fields + let processedValue = value; + if (name === "name" || name === "address" || name === "city" || name === "country") { + processedValue = value + .split(" ") + .map((word) => { + if (word.length === 0) return word; + return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(); + }) + .join(" "); + } + + setFormData((prev) => ({ + ...prev, + [name]: processedValue, + })); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + + try { + const response = await fetch("/api/profile/update", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(formData), + }); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.error || "Failed to update profile"); + } + + console.log("✅ Profile updated:", data); + toast.success("Profile updated successfully!"); + + // Navigate to profile page and force full reload to get fresh session + window.location.href = "/profile"; + } catch (error) { + toast.error("Failed to update profile. Please try again."); + console.error("❌ Error updating profile:", error); + setLoading(false); + } + }; + + if (status === "loading") { + return ( +
+
+
+

Loading...

+
+
+ ); + } + + if (status === "unauthenticated") { + return null; + } + + return ( + + {/* Hero Section */} +
+
+
+ +
+

Edit Profile

+

+ Update your account information +

+
+
+ + {/* Main Content */} +
+ {/* Back Button */} + + + Back to Profile + + + {/* Edit Form */} +
+
+ {/* Full Name */} +
+ +
+ + +
+
+ + {/* Email */} +
+ +
+ + +
+

Email cannot be changed

+
+ + {/* Phone */} +
+ +
+ + +
+
+ + {/* Address */} +
+ +
+ + +
+
+ + {/* City and Country */} +
+
+ + +
+
+ + +
+
+ + {/* Action Buttons */} +
+ + + Cancel + +
+
+
+
+
+ ); +}; + +export default EditProfilePage; diff --git a/app/profile/page.tsx b/app/profile/page.tsx index a997f1e..a3a2ca2 100644 --- a/app/profile/page.tsx +++ b/app/profile/page.tsx @@ -201,7 +201,7 @@ const ProfilePage = () => {

Manage Settings diff --git a/lib/auth.ts b/lib/auth.ts index 056d3c4..37e83e4 100644 --- a/lib/auth.ts +++ b/lib/auth.ts @@ -464,12 +464,35 @@ export const authOptions: NextAuthOptions = { async session({ session, token }) { if (session.user) { + // ALWAYS fetch the latest user data from the database to ensure fresh data + try { + const userEmail = session.user.email; + const result = await pool.query( + "SELECT user_id, name, email, picture FROM users WHERE email = $1", + [userEmail] + ); + + if (result.rows[0]) { + const dbUser = result.rows[0]; + // Update session with latest data from database + session.user.name = dbUser.name; + session.user.email = dbUser.email; + if (dbUser.picture) { + session.user.image = dbUser.picture; + } + session.user.id = dbUser.user_id; + console.log("✅ Session refreshed with latest data from DB - Name:", dbUser.name); + } + } catch (error) { + console.error("⚠️ Error fetching fresh user data in session callback:", error); + // Continue with existing token data if DB query fails + } + // Priority 1: Use the database user_id stored in JWT (fastest path) if (token.db_id) { // Ensure db_id is converted to string properly const dbIdStr = typeof token.db_id === 'string' ? token.db_id : String(token.db_id); session.user.id = dbIdStr; - console.log("✅ Session created with DB ID from JWT:", dbIdStr); } // Priority 2: For OAuth users (Google or Facebook) without stored DB ID // Check if token.role is 'google' or 'facebook' OR token.sub exists @@ -497,7 +520,6 @@ export const authOptions: NextAuthOptions = { const userId = result.rows[0].user_id; const userIdStr = typeof userId === 'string' ? userId : String(userId); session.user.id = userIdStr; - console.log("✅ OAuth user session created with DB ID:", userIdStr); } else { // Fallback to token sub if user not found in DB (shouldn't happen if upsert worked) console.warn("⚠️ User not found in database by provider ID, using token.sub:", token.sub); @@ -511,18 +533,15 @@ export const authOptions: NextAuthOptions = { // Priority 3: For credential users (employees or regular users with role), use token.id or token.sub else if (token.role && token.role !== 'google' && token.role !== 'facebook' && token.role !== 'haven') { session.user.id = token.id || token.sub!; - console.log("✅ Session created with employee/credential ID:", token.id || token.sub); } // Priority 4: Fallback to token.sub or token.id else { session.user.id = token.id || token.sub!; - console.log("⚠️ Using fallback ID:", token.id || token.sub); } // Add role if available if (token.role) { (session.user as { role?: string }).role = token.role as string; - console.log("✅ Session created with role:", token.role); } } return session;