Profile picture upload added, QR code scan fix

This commit is contained in:
Milan Paunovic
2024-11-01 04:34:59 +01:00
parent 0fbf89644f
commit 909a4344dc
7 changed files with 784 additions and 695 deletions

View File

@ -1,33 +1,36 @@
import {SafeAreaView} from "react-native-safe-area-context"; import {SafeAreaView} from "react-native-safe-area-context";
import {Button, Colors, Dialog, LoaderScreen, Text, View} from "react-native-ui-lib"; import {Button, Colors, Dialog, LoaderScreen, Text, View} from "react-native-ui-lib";
import React, {useState} from "react"; import React, {useCallback, useState} from "react";
import {useRouter} from "expo-router"; import {useRouter} from "expo-router";
import QRIcon from "@/assets/svgs/QRIcon"; import QRIcon from "@/assets/svgs/QRIcon";
import Toast from "react-native-toast-message";
import {Camera, CameraView} from "expo-camera"; import {Camera, CameraView} from "expo-camera";
import {useLoginWithQrCode} from "@/hooks/firebase/useLoginWithQrCode"; import {useLoginWithQrCode} from "@/hooks/firebase/useLoginWithQrCode";
import {useAuthContext} from "@/contexts/AuthContext";
import debounce from "debounce";
export default function Screen() { export default function Screen() {
const router = useRouter() const router = useRouter()
const {setRedirectOverride} = useAuthContext()
const [hasPermission, setHasPermission] = useState<boolean | null>(null); const [hasPermission, setHasPermission] = useState<boolean | null>(null);
const [showCameraDialog, setShowCameraDialog] = useState<boolean>(false); const [showCameraDialog, setShowCameraDialog] = useState<boolean>(false);
const {mutateAsync: signInWithQrCode, isLoading} = useLoginWithQrCode(); const {mutateAsync: signInWithQrCode, isLoading} = useLoginWithQrCode();
const debouncedRouterReplace = useCallback(
debounce(() => {
router.push("/(unauth)/cal_sync");
}, 300),
[]
);
const handleQrCodeScanned = async ({data}: { data: string }) => { const handleQrCodeScanned = async ({data}: { data: string }) => {
setShowCameraDialog(false); setShowCameraDialog(false);
setRedirectOverride(true);
try { try {
await signInWithQrCode({userId: data}); await signInWithQrCode({userId: data});
Toast.show({ debouncedRouterReplace()
type: "success",
text1: "Login successful with QR code!",
});
} catch (err) { } catch (err) {
Toast.show({ console.log(err)
type: "error",
text1: "Error logging in with QR code",
text2: `${err}`,
});
} }
}; };

View File

@ -225,7 +225,7 @@ export const EventCalendar: React.FC<EventCalendarProps> = React.memo(
mode={mode} mode={mode}
enableEnrichedEvents={true} enableEnrichedEvents={true}
sortedMonthView sortedMonthView
enrichedEventsByDate={enrichedEvents} // enrichedEventsByDate={enrichedEvents}
events={filteredEvents} events={filteredEvents}
// eventCellStyle={memoizedEventCellStyle} // eventCellStyle={memoizedEventCellStyle}
onPressEvent={handlePressEvent} onPressEvent={handlePressEvent}

View File

@ -1,25 +1,19 @@
import { import {FloatingButton, Text, TouchableOpacity, View,} from "react-native-ui-lib";
FloatingButton,
Text,
TouchableOpacity,
View,
} from "react-native-ui-lib";
import React, {useState} from "react"; import React, {useState} from "react";
import {Ionicons} from "@expo/vector-icons"; import {Ionicons} from "@expo/vector-icons";
import {ScrollView, StyleSheet} from "react-native"; import {ScrollView, StyleSheet} from "react-native";
import MyProfile from "./user_settings_views/MyProfile"; import MyProfile from "./user_settings_views/MyProfile";
import MyGroup from "./user_settings_views/MyGroup"; import MyGroup from "./user_settings_views/MyGroup";
import { useAtom } from "jotai"; import {useAtom, useSetAtom} from "jotai";
import {settingsPageIndex, userSettingsView} from "../calendar/atoms"; import {settingsPageIndex, userSettingsView} from "../calendar/atoms";
import { AuthContextProvider } from "@/contexts/AuthContext";
import PlusIcon from "@/assets/svgs/PlusIcon"; import PlusIcon from "@/assets/svgs/PlusIcon";
const UserSettings = () => { const UserSettings = () => {
const [pageIndex, setPageIndex] = useAtom(settingsPageIndex); const setPageIndex = useSetAtom(settingsPageIndex);
const [userView, setUserView] = useAtom(userSettingsView); const [userView, setUserView] = useAtom(userSettingsView);
const [onNewUserClick, setOnNewUserClick] = useState<(boolean)>(false); const [onNewUserClick, setOnNewUserClick] = useState<(boolean)>(false);
return ( return (
<AuthContextProvider>
<View flexG> <View flexG>
<ScrollView style={{paddingBottom: 20, minHeight: "100%"}}> <ScrollView style={{paddingBottom: 20, minHeight: "100%"}}>
<TouchableOpacity <TouchableOpacity
@ -98,7 +92,6 @@ const UserSettings = () => {
/> />
)} )}
</View> </View>
</AuthContextProvider>
); );
}; };

View File

@ -3,8 +3,7 @@ import {
Button, Button,
Card, Card,
Colors, Colors,
Dialog, Dialog, Image,
FloatingButton,
KeyboardAwareScrollView, KeyboardAwareScrollView,
PanningProvider, PanningProvider,
Picker, Picker,
@ -28,10 +27,10 @@ import CircledXIcon from "@/assets/svgs/CircledXIcon";
import ProfileIcon from "@/assets/svgs/ProfileIcon"; import ProfileIcon from "@/assets/svgs/ProfileIcon";
import NavToDosIcon from "@/assets/svgs/NavToDosIcon"; import NavToDosIcon from "@/assets/svgs/NavToDosIcon";
import Ionicons from "@expo/vector-icons/Ionicons"; import Ionicons from "@expo/vector-icons/Ionicons";
import KeyboardManager, { import KeyboardManager, {PreviousNextView,} from "react-native-keyboard-manager";
PreviousNextView,
} from "react-native-keyboard-manager";
import {ScrollView} from "react-native-gesture-handler"; import {ScrollView} from "react-native-gesture-handler";
import {useUploadProfilePicture} from "@/hooks/useUploadProfilePicture";
import {ImagePickerAsset} from "expo-image-picker";
type MyGroupProps = { type MyGroupProps = {
onNewUserClick: boolean; onNewUserClick: boolean;
@ -47,6 +46,8 @@ const MyGroup: React.FC<MyGroupProps> = ({ onNewUserClick, setOnNewUserClick })
const [lastName, setLastName] = useState(""); const [lastName, setLastName] = useState("");
const [email, setEmail] = useState(""); const [email, setEmail] = useState("");
const [newUserId, setNewUserId] = useState("")
const lNameRef = useRef<TextFieldRef>(null); const lNameRef = useRef<TextFieldRef>(null);
const emailRef = useRef<TextFieldRef>(null); const emailRef = useRef<TextFieldRef>(null);
@ -57,6 +58,7 @@ const MyGroup: React.FC<MyGroupProps> = ({ onNewUserClick, setOnNewUserClick })
const {mutateAsync: createSubUser, isLoading, isError} = useCreateSubUser(); const {mutateAsync: createSubUser, isLoading, isError} = useCreateSubUser();
const {data: familyMembers} = useGetFamilyMembers(true); const {data: familyMembers} = useGetFamilyMembers(true);
const {user} = useAuthContext(); const {user} = useAuthContext();
const {pickImage, changeProfilePicture, handleClearImage, pfpUri, profileImageAsset} = useUploadProfilePicture(newUserId)
const parents = const parents =
familyMembers?.filter((x) => x.userType === ProfileType.PARENT) ?? []; familyMembers?.filter((x) => x.userType === ProfileType.PARENT) ?? [];
@ -100,10 +102,17 @@ const MyGroup: React.FC<MyGroupProps> = ({ onNewUserClick, setOnNewUserClick })
setOnNewUserClick(false); setOnNewUserClick(false);
if (res?.data?.userId) { if (res?.data?.userId) {
if (profileImageAsset) {
await changeProfilePicture(profileImageAsset)
setShowQRCodeDialog(res.data.userId);
} else {
setTimeout(() => { setTimeout(() => {
setShowQRCodeDialog(res.data.userId); setShowQRCodeDialog(res.data.userId);
}, 500); }, 500);
} }
handleClearImage()
}
} }
}; };
@ -331,6 +340,14 @@ const MyGroup: React.FC<MyGroupProps> = ({ onNewUserClick, setOnNewUserClick })
<View style={styles.divider} spread/> <View style={styles.divider} spread/>
<View row centerV gap-20 marginV-20> <View row centerV gap-20 marginV-20>
{pfpUri ? (
<Image
height={65.54}
width={65.54}
style={{borderRadius: 25}}
source={{uri: pfpUri}}
/>
) : (
<View <View
height={65.54} height={65.54}
width={65.54} width={65.54}
@ -341,11 +358,22 @@ const MyGroup: React.FC<MyGroupProps> = ({ onNewUserClick, setOnNewUserClick })
style={{borderRadius: 25}} style={{borderRadius: 25}}
center center
/> />
<TouchableOpacity onPress={() => {}}> )}
{pfpUri ? (
<TouchableOpacity onPress={handleClearImage}>
<Text color={Colors.red40} style={styles.jakarta13} marginL-15>
Clear user photo
</Text>
</TouchableOpacity>
) : (
<TouchableOpacity onPress={pickImage}>
<Text color="#50be0c" style={styles.jakarta13} marginL-15> <Text color="#50be0c" style={styles.jakarta13} marginL-15>
Upload User Profile Photo Upload User Profile Photo
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
)}
</View> </View>
<Text style={styles.jakarta12}>Member Status</Text> <Text style={styles.jakarta12}>Member Status</Text>

View File

@ -65,7 +65,6 @@ const MyProfile = () => {
if (profileData) { if (profileData) {
setFirstName(profileData.firstName || ""); setFirstName(profileData.firstName || "");
setLastName(profileData.lastName || ""); setLastName(profileData.lastName || "");
// setProfileImage(profileData.pfp || null);
setTimeZone( setTimeZone(
profileData.timeZone || Localization.getCalendars()[0].timeZone! profileData.timeZone || Localization.getCalendars()[0].timeZone!
); );

View File

@ -5,7 +5,7 @@ import { useAuthContext } from "@/contexts/AuthContext";
import * as ImagePicker from "expo-image-picker"; import * as ImagePicker from "expo-image-picker";
import {Platform} from "react-native"; import {Platform} from "react-native";
export const useChangeProfilePicture = () => { export const useChangeProfilePicture = (customUserId?: string) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const {user, refreshProfileData} = useAuthContext(); const {user, refreshProfileData} = useAuthContext();
@ -38,10 +38,12 @@ export const useChangeProfilePicture = () => {
const downloadURL = await reference.getDownloadURL(); const downloadURL = await reference.getDownloadURL();
console.log("Download URL:", downloadURL); console.log("Download URL:", downloadURL);
if(!customUserId) {
await firestore() await firestore()
.collection("Profiles") .collection("Profiles")
.doc(user?.uid) .doc(user?.uid)
.update({pfp: downloadURL}); .update({pfp: downloadURL});
}
} catch (e) { } catch (e) {
console.error("Error uploading profile picture:", e); console.error("Error uploading profile picture:", e);
@ -50,8 +52,10 @@ export const useChangeProfilePicture = () => {
}, },
onSuccess: () => { onSuccess: () => {
// Invalidate queries to refresh profile data // Invalidate queries to refresh profile data
if (!customUserId) {
queryClient.invalidateQueries("Profiles"); queryClient.invalidateQueries("Profiles");
refreshProfileData(); refreshProfileData();
}
}, },
}); });
}; };

View File

@ -0,0 +1,62 @@
import {useState} from "react";
import * as ImagePicker from "expo-image-picker";
import {useChangeProfilePicture} from "@/hooks/firebase/useChangeProfilePicture";
import {useUpdateUserData} from "@/hooks/firebase/useUpdateUserData";
export const useUploadProfilePicture = (customUserId?: string, existingPfp?: string) => {
const [profileImage, setProfileImage] = useState<
string | ImagePicker.ImagePickerAsset | null
>(existingPfp || null);
const [profileImageAsset, setProfileImageAsset] = useState<
ImagePicker.ImagePickerAsset | null
>(null);
const {mutateAsync: updateUserData} = useUpdateUserData();
const {mutateAsync: changeProfilePicture} = useChangeProfilePicture(customUserId);
const pickImage = async () => {
const permissionResult =
await ImagePicker.requestMediaLibraryPermissionsAsync();
if (!permissionResult.granted) {
alert("Permission to access camera roll is required!");
return;
}
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
aspect: [1, 1],
quality: 1,
});
if (!result.canceled) {
setProfileImage(result.assets[0].uri);
setProfileImageAsset(result.assets[0]);
if (!customUserId) {
await changeProfilePicture(result.assets[0]);
}
}
};
const handleClearImage = async () => {
if (!customUserId) {
await updateUserData({newUserData: {pfp: null}});
}
setProfileImage(null);
};
const pfpUri =
profileImage && typeof profileImage === "object" && "uri" in profileImage
? profileImage.uri
: profileImage;
return {
pfpUri,
profileImage,
profileImageAsset,
handleClearImage,
pickImage,
changeProfilePicture
}
}