From 78be48ffd02db6d61f46bb530b5661387655c289 Mon Sep 17 00:00:00 2001 From: Tola Leng Date: Sat, 17 May 2025 17:55:59 +0800 Subject: [PATCH] Fix: Ensure email change request is sent. The email change request was failing with a 400 error. This commit addresses the issue by ensuring the email change request is correctly sent when a user updates their email in the profile dashboard. --- .../src/components/profile/ProfileContent.tsx | 14 +++- .../components/profile/UpdateProfileForm.tsx | 21 ++++- application/src/pages/Profile.tsx | 81 ++++++++++--------- application/src/services/authService.ts | 9 ++- application/src/services/userService.ts | 60 +++++--------- 5 files changed, 97 insertions(+), 88 deletions(-) diff --git a/application/src/components/profile/ProfileContent.tsx b/application/src/components/profile/ProfileContent.tsx index 7dc80ae..f7c8c7d 100644 --- a/application/src/components/profile/ProfileContent.tsx +++ b/application/src/components/profile/ProfileContent.tsx @@ -1,5 +1,5 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; import { User } from "@/services/userService"; import { UserProfileDetails } from "./UserProfileDetails"; @@ -9,11 +9,19 @@ import { UpdateProfileForm } from "./UpdateProfileForm"; interface ProfileContentProps { currentUser: User | null; + onUserUpdated?: () => Promise; } -export function ProfileContent({ currentUser }: ProfileContentProps) { +export function ProfileContent({ currentUser, onUserUpdated }: ProfileContentProps) { const [activeTab, setActiveTab] = useState("details"); + // When active tab changes, refresh user data if needed + useEffect(() => { + if (activeTab === "details" && onUserUpdated) { + onUserUpdated(); + } + }, [activeTab, onUserUpdated]); + if (!currentUser) { return ( @@ -71,4 +79,4 @@ export function ProfileContent({ currentUser }: ProfileContentProps) { ); -} +} \ No newline at end of file diff --git a/application/src/components/profile/UpdateProfileForm.tsx b/application/src/components/profile/UpdateProfileForm.tsx index b335e2d..ac601ec 100644 --- a/application/src/components/profile/UpdateProfileForm.tsx +++ b/application/src/components/profile/UpdateProfileForm.tsx @@ -9,7 +9,7 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from " import { Input } from "@/components/ui/input"; import { useToast } from "@/hooks/use-toast"; import { authService } from "@/services/authService"; -import { AlertCircle } from "lucide-react"; +import { AlertCircle, CheckCircle } from "lucide-react"; import { Alert, AlertDescription } from "@/components/ui/alert"; // Profile update form schema @@ -35,6 +35,7 @@ export function UpdateProfileForm({ user }: UpdateProfileFormProps) { const [isSubmitting, setIsSubmitting] = useState(false); const [emailChangeRequested, setEmailChangeRequested] = useState(false); const [updateError, setUpdateError] = useState(null); + const [updateSuccess, setUpdateSuccess] = useState(null); const { toast } = useToast(); // Initialize the form with current user data @@ -50,6 +51,7 @@ export function UpdateProfileForm({ user }: UpdateProfileFormProps) { async function onSubmit(data: ProfileFormValues) { setIsSubmitting(true); setUpdateError(null); + setUpdateSuccess(null); setEmailChangeRequested(false); try { @@ -62,8 +64,9 @@ export function UpdateProfileForm({ user }: UpdateProfileFormProps) { const updateData = { full_name: data.full_name, username: data.username, + // Only include email if it's changed email: isEmailChanged ? data.email : undefined, - // Only set emailVisibility if email is being changed + // Always set emailVisibility to true if email is changing emailVisibility: isEmailChanged ? true : undefined }; @@ -78,11 +81,14 @@ export function UpdateProfileForm({ user }: UpdateProfileFormProps) { // If email was changed, show a specific message if (isEmailChanged) { setEmailChangeRequested(true); + setUpdateSuccess("A verification email has been sent to your new email address. Please check your inbox to complete the change."); toast({ - title: "Email change requested", + title: "Email verification sent", description: "A verification email has been sent to your new email address. Please check your inbox and follow the instructions to complete the change.", + variant: "default", }); } else { + setUpdateSuccess("Your profile information has been updated successfully."); toast({ title: "Profile updated", description: "Your profile information has been updated successfully.", @@ -118,6 +124,15 @@ export function UpdateProfileForm({ user }: UpdateProfileFormProps) { )} + {updateSuccess && ( + + + + {updateSuccess} + + + )} + {emailChangeRequested && ( diff --git a/application/src/pages/Profile.tsx b/application/src/pages/Profile.tsx index 20c60b3..0ba6aed 100644 --- a/application/src/pages/Profile.tsx +++ b/application/src/pages/Profile.tsx @@ -1,5 +1,5 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useCallback } from "react"; import { Header } from "@/components/dashboard/Header"; import { Sidebar } from "@/components/dashboard/Sidebar"; import { authService } from "@/services/authService"; @@ -24,49 +24,49 @@ const Profile = () => { const { toast } = useToast(); // Fetch complete user data - useEffect(() => { - const fetchUserData = async () => { - if (!currentUser?.id) { - console.error("No current user ID found"); - setLoading(false); - setError("No user found. Please login again."); - return; - } + const fetchUserData = useCallback(async () => { + if (!currentUser?.id) { + console.error("No current user ID found"); + setLoading(false); + setError("No user found. Please login again."); + return; + } + + try { + console.log("Fetching user data for ID:", currentUser.id); + const data = await userService.getUser(currentUser.id); + console.log("Received user data:", data); - try { - console.log("Fetching user data for ID:", currentUser.id); - const data = await userService.getUser(currentUser.id); - console.log("Received user data:", data); - - if (data) { - setUserData(data); - setError(null); - } else { - console.error("No user data returned"); - setError("Could not load user data"); - toast({ - title: "Error", - description: "Could not load user data. Please try again later.", - variant: "destructive", - }); - } - } catch (error) { - const errorMessage = error instanceof Error ? error.message : "Unknown error"; - console.error("Failed to load user data:", error); - setError(errorMessage); + if (data) { + setUserData(data); + setError(null); + } else { + console.error("No user data returned"); + setError("Could not load user data"); toast({ title: "Error", - description: "Failed to load user profile. Please try again later.", + description: "Could not load user data. Please try again later.", variant: "destructive", }); - } finally { - setLoading(false); } - }; - - fetchUserData(); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : "Unknown error"; + console.error("Failed to load user data:", error); + setError(errorMessage); + toast({ + title: "Error", + description: "Failed to load user profile. Please try again later.", + variant: "destructive", + }); + } finally { + setLoading(false); + } }, [currentUser?.id, toast]); + useEffect(() => { + fetchUserData(); + }, [fetchUserData]); + // Handle logout const handleLogout = () => { authService.logout(); @@ -118,14 +118,17 @@ const Profile = () => {

{error}

) : ( - + )} @@ -133,4 +136,4 @@ const Profile = () => { ); }; -export default Profile; +export default Profile; \ No newline at end of file diff --git a/application/src/services/authService.ts b/application/src/services/authService.ts index ad4542b..009c32c 100644 --- a/application/src/services/authService.ts +++ b/application/src/services/authService.ts @@ -60,17 +60,20 @@ export const authService = { return pb.authStore.isValid; }, - // New method to refresh user data after updates + // Method to refresh user data after updates async refreshUserData(): Promise { if (!pb.authStore.isValid || !pb.authStore.model) return; try { // Fetch the latest user data from the server const userId = (pb.authStore.model as any).id; + console.log("Refreshing user data for ID:", userId); + + // Use the getOne method directly to refresh the auth store await pb.collection('users').getOne(userId); - // PocketBase will automatically update the authStore + console.log("User data refreshed successfully"); } catch (error) { console.error('Failed to refresh user data:', error); } } -}; +}; \ No newline at end of file diff --git a/application/src/services/userService.ts b/application/src/services/userService.ts index 8b3a51d..e79ee55 100644 --- a/application/src/services/userService.ts +++ b/application/src/services/userService.ts @@ -1,3 +1,4 @@ + import { pb } from "@/lib/pocketbase"; export interface User { @@ -89,7 +90,7 @@ export const userService = { return currentUser; } - // Handle email updates separately using the request-email-change endpoint + // Handle email updates separately const hasEmailChange = cleanData.email !== undefined; const emailToUpdate = hasEmailChange ? cleanData.email : null; @@ -97,7 +98,11 @@ export const userService = { if (hasEmailChange) { console.log("Email change detected, will handle separately:", emailToUpdate); delete cleanData.email; - delete cleanData.emailVisibility; // Don't set this yet, it will be set after email change + + // For email changes, we should always set emailVisibility + if (cleanData.emailVisibility === undefined) { + cleanData.emailVisibility = true; + } } let updatedUser: User | null = null; @@ -106,25 +111,14 @@ export const userService = { if (Object.keys(cleanData).length > 0) { console.log("Final update payload to PocketBase:", cleanData); - // Use the direct API approach for more control over the update process - const apiUrl = `${pb.baseUrl}/api/collections/users/records/${id}`; - const response = await fetch(apiUrl, { - method: 'PATCH', - headers: { - 'Content-Type': 'application/json', - 'Authorization': pb.authStore.token - }, - body: JSON.stringify(cleanData) - }); - - if (!response.ok) { - const errorData = await response.json(); - console.error("PocketBase API error:", errorData); - throw new Error(errorData.message || "Failed to update user"); + try { + // Use PocketBase's update method for the regular fields + updatedUser = await pb.collection('users').update(id, cleanData) as unknown as User; + console.log("PocketBase update response for regular fields:", updatedUser); + } catch (error) { + console.error("Error updating user regular fields:", error); + throw error; } - - updatedUser = await response.json() as unknown as User; - console.log("PocketBase update response for regular fields:", updatedUser); } else { // If no other fields to update, get the current user updatedUser = await this.getUser(id); @@ -134,32 +128,18 @@ export const userService = { if (emailToUpdate) { try { console.log("Processing email change request for new email:", emailToUpdate); - // Use the request-email-change endpoint directly - const emailChangeUrl = `${pb.baseUrl}/api/collections/users/request-email-change`; - const emailChangeResponse = await fetch(emailChangeUrl, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': pb.authStore.token - }, - body: JSON.stringify({ - newEmail: emailToUpdate - }) - }); - if (!emailChangeResponse.ok) { - const errorData = await emailChangeResponse.json(); - console.error("Email change request failed:", errorData); - throw new Error(errorData.message || "Failed to request email change"); - } + // Use the proper API endpoint for email change requests + const emailChangeResponse = await pb.collection('users').requestEmailChange(emailToUpdate); + + console.log("Email change request response:", emailChangeResponse); + console.log("Email verification has been sent to:", emailToUpdate); - console.log("Email change request sent successfully"); // The email won't be updated immediately as verification is required // We'll return the current user data, and email will be updated after verification } catch (error) { console.error("Failed to request email change:", error); - // Continue with the function and return updated user for the fields that were updated - // We don't throw here because we want to save the other fields even if email change fails + throw new Error(`Failed to request email change: ${error instanceof Error ? error.message : "Unknown error"}`); } }