diff --git a/backend/routes/users.py b/backend/routes/users.py index 165a9ee..7d0c265 100644 --- a/backend/routes/users.py +++ b/backend/routes/users.py @@ -139,3 +139,23 @@ def update_user_email(uid: str) -> Response: return jsonify({"user": item}) except CarbonTrackError as e: abort(code=400, description=f"{e}") + +@users.route("/user/update_name/", methods=["PATCH"]) +@carbon_auth.auth.login_required +def update_user_name(user_id: str) -> Response: + try: + new_name = request.get_json().get("newName", "") + + query = {"uid": user_id} + + current_user = carbon_auth.auth.current_user() + + CarbonTrackDB.users_coll.update_one(query, {"$set": {"full_name": new_name}}) + + item = CarbonTrackDB.users_coll.find_one(query) + + item = User.from_json(item).to_json() + + return jsonify({"user": item}), 200 + except CarbonTrackError as e: + abort(code=400, description=f"{e}") diff --git a/frontend/src/APIs/UsersAPI.ts b/frontend/src/APIs/UsersAPI.ts index befb267..2b65642 100644 --- a/frontend/src/APIs/UsersAPI.ts +++ b/frontend/src/APIs/UsersAPI.ts @@ -113,7 +113,7 @@ export const UsersAPI = { // Make the request to update the email with the new token const res = await FLASK_HTTPS.patch( - routeName + `/user/update_email/${userId.toHexString()}`, + routeName + `/user/update_email/${firebaseUser.uid.toString()}`, { email }, { headers: { @@ -128,4 +128,17 @@ export const UsersAPI = { return undefined; } }, + updateUserName: async (user: User, newName: string) => { + try { + const res = await FLASK_HTTPS.patch( + routeName + `/user/update_name/${user.uid.toString()}`, + { newName }, + ); + + return res.data.user as User; + } catch (error) { + console.error('UsersAPI(frontend): updateUserEmailError:', error); + return undefined; + } + } }; diff --git a/frontend/src/screens/settings/updatePassword.tsx b/frontend/src/screens/settings/updatePassword.tsx index e37bc5f..548adc7 100644 --- a/frontend/src/screens/settings/updatePassword.tsx +++ b/frontend/src/screens/settings/updatePassword.tsx @@ -15,7 +15,6 @@ import { useNavigation } from '@react-navigation/native'; import Ionicons from '@expo/vector-icons/Ionicons'; import { useFonts } from 'expo-font'; import firebaseService from '../../utilities/firebase'; -import { type User } from '../../models/User'; import { onAuthStateChanged, getAuth, @@ -26,65 +25,76 @@ import { export type StackNavigation = StackNavigationProp; export default function UpdateProfileScreen(): JSX.Element { + const navigation = useNavigation(); const [loaded] = useFonts({ Montserrat: require('../../../assets/fonts/MontserratThinRegular.ttf'), Josefin: require('../../../assets/fonts/JosefinSansThinRegular.ttf'), }); - const [userid, setUserid] = useState(''); - const [rerenderKey, setRerenderKey] = useState(0); - const [loggedUser, setLoggedUser] = useState(undefined); - const [newPass, setNewPass] = useState(''); - - const auth = getAuth(); - onAuthStateChanged(auth, (user) => { - if (user === null) { - navigation.navigate('LogIn'); - } - }); + const [newPass, setNewPass] = useState(''); useEffect(() => { - const fetchUserData = async (): Promise => { - const user = await firebaseService.getFirebaseUser(); - setUserid(user?.uid ?? ''); + const checkAuthState = async (): Promise => { + const currentUser = await firebaseService.getFirebaseUser(); + if (currentUser === null) { + navigation.navigate('LogIn'); + } }; - void fetchUserData(); - }, [rerenderKey]); - const handleUpdatePassword = async (): Promise => { + const unsubscribe = onAuthStateChanged(getAuth(), (currentUser) => { + if (currentUser === null) { + navigation.navigate('LogIn'); + } else { + void checkAuthState(); + } + }); + + return () => { + unsubscribe(); // Cleanup on component unmount + }; + }, [navigation]); + + + const handleUpdatePassword = async ():Promise => { try { const user = await firebaseService.getFirebaseUser(); if (user != null) { - if (newPass != null && user.email != null){ + if (newPass != null && user.email != null) { + // get user password to reauth const userCreds = await promptUserForCredentials(); - if (userCreds != null){ - const creds = EmailAuthProvider.credential(user.email, userCreds.password); - await reauthenticateWithCredential(user, creds) - .then( async (result) => { - await updatePassword(user, newPass) - .then(() => alert('Password reset email has be sent. Please check your inbox for next steps. You will now be logged out.')) - .catch((error: any) => console.log('update password after alert error:', error)); - console.log('Firebase (frontend): Update password sucess (maybay)') - - await firebaseService.signOutUser(); - navigation.navigate('LogIn'); - // await sendPasswordResetEmail(auth, user.email) - // .then(() => alert('Password reset email has be sent. Please check your inbox for next steps.')) - // .catch((error: any) => console.log('Password email error:', error)) + if (userCreds != null) { + const creds = EmailAuthProvider.credential(user.email, userCreds.password); + // reauth user + try { + await reauthenticateWithCredential(user, creds); + + // after reauthing, update password + try { + await updatePassword(user, newPass); + alert('Password reset email has been sent. Please check your inbox for next steps. You will now be logged out.'); + console.log('Firebase (frontend): Update password success (maybe)'); + // sign out user after changing password + await firebaseService.signOutUser(); + navigation.navigate('LogIn'); + } catch (updateError: any) { + console.error('Updating password error:', updateError); + if (updateError.code === "auth/weak-password") { + Alert.alert('Invalid Password', 'Please make sure your password is strong enough.'); + } } - ); + } catch (reauthError: any) { + Alert.alert('Reauthentification Error', 'An error occured trying to reauthorize your account. Please try again.'); + console.error('Reauthenticating user error:', reauthError); + } } - } - - - } } catch (error: any) { - // Log any errors - console.error('Error updating email in Update Password screen: ', error); + console.error('Error handling password update: ', error); } + return await Promise.resolve(); }; + const promptUserForCredentials = async (): Promise<{ password: string } | null> => { return await new Promise((resolve) => { @@ -123,15 +133,14 @@ export default function UpdateProfileScreen(): JSX.Element { - Name + New Password: setNewPass(text)} /> - - {/* f onPress={handleUpdatePassword} */} + { void handleUpdatePassword()}}> Update Password diff --git a/frontend/src/screens/settings/updateProfile.tsx b/frontend/src/screens/settings/updateProfile.tsx index 51a05a3..28ddb14 100644 --- a/frontend/src/screens/settings/updateProfile.tsx +++ b/frontend/src/screens/settings/updateProfile.tsx @@ -7,7 +7,6 @@ import { TouchableOpacity, Image, TextInput, - Modal, Alert, } from 'react-native'; import Colors from '../../../assets/colorConstants'; @@ -84,6 +83,7 @@ export default function UpdateProfileScreen(): JSX.Element { // email update await verifyBeforeUpdateEmail(user, newEmail); + // TODO: when Error (auth/missing-new-email).] --make alert (email up to date) Alert.alert( 'A verification email has been sent to your new! Please verify it and login again.' ); @@ -121,6 +121,17 @@ export default function UpdateProfileScreen(): JSX.Element { }); }; + const handleNewName = async (): Promise => { + if (newName != null && loggedUser != null) { + const updatedUser = await UsersAPI.updateUserName(loggedUser, newName); + + if (updatedUser != null) { + setLoggedUser(updatedUser); + console.log(loggedUser.full_name) + } + } + } + const handleProfilePictureUpload = async (): Promise => { try { const result: ImagePickerResult = await launchImageLibraryAsync({ @@ -154,37 +165,40 @@ export default function UpdateProfileScreen(): JSX.Element { - + - + {void handleProfilePictureUpload}}> Edit Photo - Name + New Name: setNewName(text)} /> - Email + New Email: setNewEmail(text)} /> - - Update Profile + { void handleUpdateEmail()}}> + Update Email + + {void handleNewName()}} > + Update Name - ); + ); } const styles = StyleSheet.create({ container: { @@ -275,6 +289,7 @@ const styles = StyleSheet.create({ borderRadius: 5, alignSelf: 'center', top: '20%', + padding: 10, }, saveButtonText: { color: Colors.LIGHTFGREEN, diff --git a/frontend/src/utilities/firebase.ts b/frontend/src/utilities/firebase.ts index 8a71650..26fe591 100644 --- a/frontend/src/utilities/firebase.ts +++ b/frontend/src/utilities/firebase.ts @@ -10,7 +10,6 @@ import { updateEmail, type User, type UserCredential, - updatePassword, } from 'firebase/auth'; import ReactNativeAsyncStorage from '@react-native-async-storage/async-storage'; import { getStorage, ref as storageRef, uploadBytes, getDownloadURL } from 'firebase/storage';