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,138 +1,131 @@
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 {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 { settingsPageIndex, userSettingsView } from "../calendar/atoms";
import { AuthContextProvider } from "@/contexts/AuthContext";
import {useAtom, useSetAtom} from "jotai";
import {settingsPageIndex, userSettingsView} from "../calendar/atoms";
import PlusIcon from "@/assets/svgs/PlusIcon";
const UserSettings = () => {
const [pageIndex, setPageIndex] = useAtom(settingsPageIndex);
const [userView, setUserView] = useAtom(userSettingsView);
const [onNewUserClick, setOnNewUserClick] = useState<(boolean)>(false);
return (
<AuthContextProvider>
<View flexG>
<ScrollView style={{ paddingBottom: 20, minHeight: "100%" }}>
<TouchableOpacity
onPress={() => {
setPageIndex(0);
setUserView(true);
}}
>
<View row marginT-20 marginB-20 marginL-20 centerV>
<Ionicons
name="chevron-back"
size={14}
color="#979797"
style={{ paddingBottom: 3 }}
/>
<Text
style={{ fontFamily: "Poppins_400Regular", fontSize: 14.71 }}
color="#979797"
>
Return to main settings
</Text>
</View>
</TouchableOpacity>
<View marginH-26 flexG style={{ minHeight: "90%" }}>
<Text text60R marginB-25>
User Management
</Text>
<View style={styles.buttonSwitch} spread row>
<TouchableOpacity
onPress={() => setUserView(true)}
centerV
centerH
style={userView == true ? styles.btnSelected : styles.btnNot}
>
<View>
<Text
style={styles.btnTxt}
color={userView ? "white" : "black"}
>
My Profile
</Text>
const setPageIndex = useSetAtom(settingsPageIndex);
const [userView, setUserView] = useAtom(userSettingsView);
const [onNewUserClick, setOnNewUserClick] = useState<(boolean)>(false);
return (
<View flexG>
<ScrollView style={{paddingBottom: 20, minHeight: "100%"}}>
<TouchableOpacity
onPress={() => {
setPageIndex(0);
setUserView(true);
}}
>
<View row marginT-20 marginB-20 marginL-20 centerV>
<Ionicons
name="chevron-back"
size={14}
color="#979797"
style={{paddingBottom: 3}}
/>
<Text
style={{fontFamily: "Poppins_400Regular", fontSize: 14.71}}
color="#979797"
>
Return to main settings
</Text>
</View>
</TouchableOpacity>
<View marginH-26 flexG style={{minHeight: "90%"}}>
<Text text60R marginB-25>
User Management
</Text>
<View style={styles.buttonSwitch} spread row>
<TouchableOpacity
onPress={() => setUserView(true)}
centerV
centerH
style={userView == true ? styles.btnSelected : styles.btnNot}
>
<View>
<Text
style={styles.btnTxt}
color={userView ? "white" : "black"}
>
My Profile
</Text>
</View>
</TouchableOpacity>
<TouchableOpacity
onPress={() => setUserView(false)}
centerV
centerH
style={userView == false ? styles.btnSelected : styles.btnNot}
>
<View>
<Text
style={styles.btnTxt}
color={!userView ? "white" : "black"}
>
My Group
</Text>
</View>
</TouchableOpacity>
</View>
{userView && <MyProfile/>}
{!userView && <MyGroup onNewUserClick={onNewUserClick} setOnNewUserClick={setOnNewUserClick}/>}
</View>
</TouchableOpacity>
<TouchableOpacity
onPress={() => setUserView(false)}
centerV
centerH
style={userView == false ? styles.btnSelected : styles.btnNot}
>
<View>
<Text
style={styles.btnTxt}
color={!userView ? "white" : "black"}
>
My Group
</Text>
</View>
</TouchableOpacity>
</View>
{userView && <MyProfile />}
{!userView && <MyGroup onNewUserClick={onNewUserClick} setOnNewUserClick={setOnNewUserClick}/>}
</View>
</ScrollView>
{!userView && (
<FloatingButton
fullWidth
hideBackgroundOverlay
visible
button={{
label: " Add a user device",
iconSource: () => <PlusIcon height={13} width={14} />,
onPress: () => setOnNewUserClick(true),
style: styles.bottomButton,
labelStyle: { fontFamily: "Manrope_600SemiBold", fontSize: 15 },
}}
/>
)}
</View>
</AuthContextProvider>
);
</ScrollView>
{!userView && (
<FloatingButton
fullWidth
hideBackgroundOverlay
visible
button={{
label: " Add a user device",
iconSource: () => <PlusIcon height={13} width={14}/>,
onPress: () => setOnNewUserClick(true),
style: styles.bottomButton,
labelStyle: {fontFamily: "Manrope_600SemiBold", fontSize: 15},
}}
/>
)}
</View>
);
};
const styles = StyleSheet.create({
bottomButton: {
position: "absolute",
bottom: 15,
marginHorizontal: 28,
width: 337,
backgroundColor: "#e8156c",
height: 53.26,
},
buttonSwitch: {
borderRadius: 50,
width: "100%",
backgroundColor: "#ebebeb",
height: 45,
},
btnSelected: {
backgroundColor: "#05a8b6",
height: "100%",
width: "50%",
borderRadius: 50,
},
btnTxt: {
fontFamily: "Manrope_500Medium",
fontSize: 15,
},
btnNot: {
height: "100%",
width: "50%",
borderRadius: 50,
},
title: { fontFamily: "Manrope_600SemiBold", fontSize: 18 },
bottomButton: {
position: "absolute",
bottom: 15,
marginHorizontal: 28,
width: 337,
backgroundColor: "#e8156c",
height: 53.26,
},
buttonSwitch: {
borderRadius: 50,
width: "100%",
backgroundColor: "#ebebeb",
height: 45,
},
btnSelected: {
backgroundColor: "#05a8b6",
height: "100%",
width: "50%",
borderRadius: 50,
},
btnTxt: {
fontFamily: "Manrope_500Medium",
fontSize: 15,
},
btnNot: {
height: "100%",
width: "50%",
borderRadius: 50,
},
title: {fontFamily: "Manrope_600SemiBold", fontSize: 18},
});
export default UserSettings;

File diff suppressed because it is too large Load Diff

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

@ -1,13 +1,13 @@
import { useMutation, useQueryClient } from "react-query";
import {useMutation, useQueryClient} from "react-query";
import firestore from "@react-native-firebase/firestore";
import storage from "@react-native-firebase/storage";
import { useAuthContext } from "@/contexts/AuthContext";
import {useAuthContext} from "@/contexts/AuthContext";
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 { user, refreshProfileData } = useAuthContext();
const {user, refreshProfileData} = useAuthContext();
return useMutation({
mutationKey: ["changeProfilePicture"],
@ -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 });
.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
queryClient.invalidateQueries("Profiles");
refreshProfileData();
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
}
}