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

View File

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

View File

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

View File

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

View File

@ -65,7 +65,6 @@ const MyProfile = () => {
if (profileData) {
setFirstName(profileData.firstName || "");
setLastName(profileData.lastName || "");
// setProfileImage(profileData.pfp || null);
setTimeZone(
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 {Platform} from "react-native";
export const useChangeProfilePicture = () => {
export const useChangeProfilePicture = (customUserId?: string) => {
const queryClient = useQueryClient();
const {user, refreshProfileData} = useAuthContext();
@ -38,10 +38,12 @@ export const useChangeProfilePicture = () => {
const downloadURL = await reference.getDownloadURL();
console.log("Download URL:", downloadURL);
if(!customUserId) {
await firestore()
.collection("Profiles")
.doc(user?.uid)
.update({pfp: downloadURL});
}
} catch (e) {
console.error("Error uploading profile picture:", e);
@ -50,8 +52,10 @@ export const useChangeProfilePicture = () => {
},
onSuccess: () => {
// Invalidate queries to refresh profile data
if (!customUserId) {
queryClient.invalidateQueries("Profiles");
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
}
}