mirror of
https://github.com/urosran/cally.git
synced 2025-07-16 01:56:16 +00:00
add more options to user management
This commit is contained in:
17
assets/svgs/MenuDotsIcon.tsx
Normal file
17
assets/svgs/MenuDotsIcon.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import * as React from "react";
|
||||
import Svg, { SvgProps, Path } from "react-native-svg";
|
||||
const MenuDotsIcon = (props: SvgProps) => (
|
||||
<Svg
|
||||
width={props.width || 4}
|
||||
height={props.height || 15}
|
||||
viewBox="0 0 4 15"
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<Path
|
||||
fill={props.color || "#7C7C7C"}
|
||||
d="M.88 7.563a1.56 1.56 0 1 0 3.12 0 1.56 1.56 0 0 0-3.12 0Zm0-5.2A1.56 1.56 0 1 0 4 2.426a1.56 1.56 0 0 0-3.12-.063Zm0 10.4A1.56 1.56 0 1 0 4 12.701a1.56 1.56 0 0 0-3.12.062Z"
|
||||
/>
|
||||
</Svg>
|
||||
);
|
||||
export default MenuDotsIcon;
|
@ -98,7 +98,6 @@ const DeleteProfileDialogs: React.FC<ConfirmationDialogProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
// Empty stylesheet for future styles
|
||||
const styles = StyleSheet.create({
|
||||
confirmBtn: {
|
||||
backgroundColor: "#ff1637",
|
||||
|
@ -3,7 +3,8 @@ import {
|
||||
Button,
|
||||
Card,
|
||||
Colors,
|
||||
Dialog, Image,
|
||||
Dialog,
|
||||
Image,
|
||||
KeyboardAwareScrollView,
|
||||
PanningProvider,
|
||||
Picker,
|
||||
@ -13,31 +14,38 @@ import {
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from "react-native-ui-lib";
|
||||
import React, {useEffect, useRef, useState} from "react";
|
||||
import {ImageBackground, Platform, StyleSheet} from "react-native";
|
||||
import {PickerSingleValue} from "react-native-ui-lib/src/components/picker/types";
|
||||
import {useCreateSubUser} from "@/hooks/firebase/useCreateSubUser";
|
||||
import {ProfileType, useAuthContext} from "@/contexts/AuthContext";
|
||||
import {useGetFamilyMembers} from "@/hooks/firebase/useGetFamilyMembers";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { ImageBackground, Platform, StyleSheet } from "react-native";
|
||||
import { PickerSingleValue } from "react-native-ui-lib/src/components/picker/types";
|
||||
import { useCreateSubUser } from "@/hooks/firebase/useCreateSubUser";
|
||||
import { ProfileType, useAuthContext } from "@/contexts/AuthContext";
|
||||
import { useGetFamilyMembers } from "@/hooks/firebase/useGetFamilyMembers";
|
||||
import UserMenu from "@/components/pages/settings/user_settings_views/UserMenu";
|
||||
import {uuidv4} from "@firebase/util";
|
||||
import { uuidv4 } from "@firebase/util";
|
||||
import QRIcon from "@/assets/svgs/QRIcon";
|
||||
import EmailIcon from "@/assets/svgs/EmailIcon";
|
||||
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 {ScrollView} from "react-native-gesture-handler";
|
||||
import {useUploadProfilePicture} from "@/hooks/useUploadProfilePicture";
|
||||
import {ImagePickerAsset} from "expo-image-picker";
|
||||
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";
|
||||
import MenuDotsIcon from "@/assets/svgs/MenuDotsIcon";
|
||||
import UserOptions from "./UserOptions";
|
||||
|
||||
type MyGroupProps = {
|
||||
onNewUserClick: boolean;
|
||||
setOnNewUserClick: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
};
|
||||
|
||||
const MyGroup: React.FC<MyGroupProps> = ({onNewUserClick, setOnNewUserClick}) => {
|
||||
const MyGroup: React.FC<MyGroupProps> = ({
|
||||
onNewUserClick,
|
||||
setOnNewUserClick,
|
||||
}) => {
|
||||
const [showAddUserDialog, setShowAddUserDialog] = useState(false);
|
||||
const [selectedStatus, setSelectedStatus] = useState<
|
||||
string | PickerSingleValue
|
||||
@ -46,7 +54,9 @@ const MyGroup: React.FC<MyGroupProps> = ({onNewUserClick, setOnNewUserClick}) =>
|
||||
const [lastName, setLastName] = useState("");
|
||||
const [email, setEmail] = useState("");
|
||||
|
||||
const [newUserId, setNewUserId] = useState("")
|
||||
const { profileData } = useAuthContext();
|
||||
|
||||
const [newUserId, setNewUserId] = useState("");
|
||||
|
||||
const lNameRef = useRef<TextFieldRef>(null);
|
||||
const emailRef = useRef<TextFieldRef>(null);
|
||||
@ -55,10 +65,16 @@ const MyGroup: React.FC<MyGroupProps> = ({onNewUserClick, setOnNewUserClick}) =>
|
||||
false
|
||||
);
|
||||
|
||||
const {mutateAsync: createSubUser, isLoading, isError} = useCreateSubUser();
|
||||
const {data: familyMembers} = useGetFamilyMembers(true);
|
||||
const {user} = useAuthContext();
|
||||
const {pickImage, changeProfilePicture, handleClearImage, pfpUri, profileImageAsset} = useUploadProfilePicture(newUserId)
|
||||
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) ?? [];
|
||||
@ -103,7 +119,7 @@ const MyGroup: React.FC<MyGroupProps> = ({onNewUserClick, setOnNewUserClick}) =>
|
||||
|
||||
if (res?.data?.userId) {
|
||||
if (profileImageAsset) {
|
||||
await changeProfilePicture(profileImageAsset)
|
||||
await changeProfilePicture(profileImageAsset);
|
||||
setShowQRCodeDialog(res.data.userId);
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
@ -111,7 +127,7 @@ const MyGroup: React.FC<MyGroupProps> = ({onNewUserClick, setOnNewUserClick}) =>
|
||||
}, 500);
|
||||
}
|
||||
|
||||
handleClearImage()
|
||||
handleClearImage();
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -156,11 +172,11 @@ const MyGroup: React.FC<MyGroupProps> = ({onNewUserClick, setOnNewUserClick}) =>
|
||||
<ImageBackground
|
||||
style={styles.pfp}
|
||||
borderRadius={10.56}
|
||||
source={{uri: member.pfp || undefined}}
|
||||
source={{ uri: member.pfp || undefined }}
|
||||
/>
|
||||
) : (
|
||||
<View
|
||||
style={[styles.pfp, {backgroundColor: "#ea156d"}]}
|
||||
style={[styles.pfp, { backgroundColor: "#ea156d" }]}
|
||||
/>
|
||||
)}
|
||||
<View row marginL-10 centerV>
|
||||
@ -168,7 +184,7 @@ const MyGroup: React.FC<MyGroupProps> = ({onNewUserClick, setOnNewUserClick}) =>
|
||||
{member.firstName} {member.lastName}
|
||||
</Text>
|
||||
</View>
|
||||
<View flexG/>
|
||||
<View flexG />
|
||||
<View row centerV gap-10>
|
||||
<Text style={styles.userType}>
|
||||
{member.userType === ProfileType.PARENT
|
||||
@ -180,6 +196,8 @@ const MyGroup: React.FC<MyGroupProps> = ({onNewUserClick, setOnNewUserClick}) =>
|
||||
showQRCodeDialog={showQRCodeDialog === member?.uid}
|
||||
user={member}
|
||||
/>
|
||||
{profileData?.userType === ProfileType.PARENT &&
|
||||
user?.uid != member.uid && <UserOptions user={member} />}
|
||||
</View>
|
||||
</Card>
|
||||
))}
|
||||
@ -202,7 +220,7 @@ const MyGroup: React.FC<MyGroupProps> = ({onNewUserClick, setOnNewUserClick}) =>
|
||||
padding-10
|
||||
>
|
||||
<Avatar
|
||||
source={{uri: member?.pfp ?? undefined}}
|
||||
source={{ uri: member?.pfp ?? undefined }}
|
||||
size={40}
|
||||
backgroundColor={Colors.grey60}
|
||||
/>
|
||||
@ -215,7 +233,7 @@ const MyGroup: React.FC<MyGroupProps> = ({onNewUserClick, setOnNewUserClick}) =>
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View flex-1/>
|
||||
<View flex-1 />
|
||||
|
||||
<UserMenu
|
||||
setShowQRCodeDialog={(val) => setShowQRCodeDialog(val)}
|
||||
@ -243,7 +261,7 @@ const MyGroup: React.FC<MyGroupProps> = ({onNewUserClick, setOnNewUserClick}) =>
|
||||
padding-10
|
||||
>
|
||||
<Avatar
|
||||
source={{uri: member?.pfp ?? undefined}}
|
||||
source={{ uri: member?.pfp ?? undefined }}
|
||||
size={40}
|
||||
backgroundColor={Colors.grey60}
|
||||
/>
|
||||
@ -254,7 +272,7 @@ const MyGroup: React.FC<MyGroupProps> = ({onNewUserClick, setOnNewUserClick}) =>
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View flex-1/>
|
||||
<View flex-1 />
|
||||
|
||||
<UserMenu
|
||||
setShowQRCodeDialog={(val) => setShowQRCodeDialog(val)}
|
||||
@ -286,7 +304,7 @@ const MyGroup: React.FC<MyGroupProps> = ({onNewUserClick, setOnNewUserClick}) =>
|
||||
</Text>
|
||||
|
||||
<Button backgroundColor={"#FD1775"} style={styles.dialogBtn}>
|
||||
<QRIcon/>
|
||||
<QRIcon />
|
||||
<Text style={styles.dialogBtnLbl} marginL-7>
|
||||
Show a QR Code
|
||||
</Text>
|
||||
@ -301,7 +319,7 @@ const MyGroup: React.FC<MyGroupProps> = ({onNewUserClick, setOnNewUserClick}) =>
|
||||
}, 500);
|
||||
}}
|
||||
>
|
||||
<EmailIcon/>
|
||||
<EmailIcon />
|
||||
<Text style={styles.dialogBtnLbl} marginL-7>
|
||||
Enter email address
|
||||
</Text>
|
||||
@ -326,7 +344,7 @@ const MyGroup: React.FC<MyGroupProps> = ({onNewUserClick, setOnNewUserClick}) =>
|
||||
<KeyboardAwareScrollView>
|
||||
<Card padding-25 style={styles.dialogCard}>
|
||||
<View row spread>
|
||||
<Text style={{fontFamily: "Manrope_500Medium", fontSize: 16}}>
|
||||
<Text style={{ fontFamily: "Manrope_500Medium", fontSize: 16 }}>
|
||||
New User Information
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
@ -334,35 +352,39 @@ const MyGroup: React.FC<MyGroupProps> = ({onNewUserClick, setOnNewUserClick}) =>
|
||||
setOnNewUserClick(false);
|
||||
}}
|
||||
>
|
||||
<CircledXIcon/>
|
||||
<CircledXIcon />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<View style={styles.divider} spread/>
|
||||
<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}}
|
||||
style={{ borderRadius: 25 }}
|
||||
source={{ uri: pfpUri }}
|
||||
/>
|
||||
) : (
|
||||
<View
|
||||
height={65.54}
|
||||
width={65.54}
|
||||
children={
|
||||
<ProfileIcon color={"#d6d6d6"} width={37} height={37}/>
|
||||
<ProfileIcon color={"#d6d6d6"} width={37} height={37} />
|
||||
}
|
||||
backgroundColor={Colors.grey60}
|
||||
style={{borderRadius: 25}}
|
||||
style={{ borderRadius: 25 }}
|
||||
center
|
||||
/>
|
||||
)}
|
||||
|
||||
{pfpUri ? (
|
||||
<TouchableOpacity onPress={handleClearImage}>
|
||||
<Text color={Colors.red40} style={styles.jakarta13} marginL-15>
|
||||
<Text
|
||||
color={Colors.red40}
|
||||
style={styles.jakarta13}
|
||||
marginL-15
|
||||
>
|
||||
Clear user photo
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
@ -373,7 +395,6 @@ const MyGroup: React.FC<MyGroupProps> = ({onNewUserClick, setOnNewUserClick}) =>
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
|
||||
</View>
|
||||
|
||||
<Text style={styles.jakarta12}>Member Status</Text>
|
||||
@ -397,15 +418,15 @@ const MyGroup: React.FC<MyGroupProps> = ({onNewUserClick, setOnNewUserClick}) =>
|
||||
>
|
||||
<Ionicons
|
||||
name={"chevron-down"}
|
||||
style={{alignSelf: "center"}}
|
||||
style={{ alignSelf: "center" }}
|
||||
size={20}
|
||||
color={"#000000"}
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
>
|
||||
<Picker.Item label="Child" value={ProfileType.CHILD}/>
|
||||
<Picker.Item label="Parent" value={ProfileType.PARENT}/>
|
||||
<Picker.Item label="Child" value={ProfileType.CHILD} />
|
||||
<Picker.Item label="Parent" value={ProfileType.PARENT} />
|
||||
<Picker.Item
|
||||
label="Caregiver"
|
||||
value={ProfileType.CAREGIVER}
|
||||
@ -488,8 +509,8 @@ const MyGroup: React.FC<MyGroupProps> = ({onNewUserClick, setOnNewUserClick}) =>
|
||||
fontSize: 15,
|
||||
marginLeft: 7,
|
||||
}}
|
||||
style={{marginTop: 20, backgroundColor: "#fd1775"}}
|
||||
iconSource={() => <NavToDosIcon width={22} color={"white"}/>}
|
||||
style={{ marginTop: 20, backgroundColor: "#fd1775" }}
|
||||
iconSource={() => <NavToDosIcon width={22} color={"white"} />}
|
||||
onPress={handleCreateSubUser}
|
||||
/>
|
||||
</Card>
|
||||
@ -505,7 +526,7 @@ const styles = StyleSheet.create({
|
||||
height: 47,
|
||||
width: 279,
|
||||
},
|
||||
dialogTitle: {fontFamily: "Manrope_600SemiBold", fontSize: 22},
|
||||
dialogTitle: { fontFamily: "Manrope_600SemiBold", fontSize: 22 },
|
||||
dialogBackBtn: {
|
||||
fontFamily: "PlusJakartaSans_500Medium",
|
||||
fontSize: 15,
|
||||
@ -593,7 +614,7 @@ const styles = StyleSheet.create({
|
||||
fontSize: 15,
|
||||
color: "white",
|
||||
},
|
||||
divider: {height: 0.7, backgroundColor: "#e6e6e6", width: "100%"},
|
||||
divider: { height: 0.7, backgroundColor: "#e6e6e6", width: "100%" },
|
||||
jakarta12: {
|
||||
fontFamily: "PlusJakartaSans_500Medium",
|
||||
fontSize: 12,
|
||||
@ -603,7 +624,7 @@ const styles = StyleSheet.create({
|
||||
fontFamily: "PlusJakartaSans_500Medium",
|
||||
fontSize: 13,
|
||||
},
|
||||
pfp: {aspectRatio: 1, width: 37.03, borderRadius: 10.56},
|
||||
pfp: { aspectRatio: 1, width: 37.03, borderRadius: 10.56 },
|
||||
userType: {
|
||||
fontFamily: "Manrope_500Medium",
|
||||
fontSize: 12,
|
||||
|
140
components/pages/settings/user_settings_views/UserOptions.tsx
Normal file
140
components/pages/settings/user_settings_views/UserOptions.tsx
Normal file
@ -0,0 +1,140 @@
|
||||
import { Platform, StyleSheet } from "react-native";
|
||||
import React, { useState } from "react";
|
||||
import { MenuView } from "@react-native-menu/menu";
|
||||
import { Dialog, Text, View, Button } from "react-native-ui-lib";
|
||||
import MenuDotsIcon from "@/assets/svgs/MenuDotsIcon";
|
||||
import { UserProfile } from "@/hooks/firebase/types/profileTypes";
|
||||
import { useRemoveSubUser } from "@/hooks/firebase/useRemoveSubUser";
|
||||
|
||||
const UserOptions = ({ user }: { user: UserProfile }) => {
|
||||
const [visible, setVisible] = useState<boolean>(false);
|
||||
const { mutateAsync: removeSubUser } = useRemoveSubUser();
|
||||
|
||||
const handleDeleteUser = async () => {
|
||||
try {
|
||||
if (user.uid) await removeSubUser(user.uid);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
setVisible(false);
|
||||
}
|
||||
};
|
||||
|
||||
const menuOptions = [
|
||||
{
|
||||
id: "edit",
|
||||
title: "Edit User",
|
||||
onPress: () => console.log("Edit User here"),
|
||||
image: Platform.select({
|
||||
ios: "pencil.and.ellipsis.rectangle",
|
||||
android: "ic_menu_edit",
|
||||
}),
|
||||
imageColor: "#7300d4",
|
||||
titleColor: "#7300d4",
|
||||
},
|
||||
{
|
||||
id: "delete",
|
||||
title: "Delete User",
|
||||
attributes: {
|
||||
destructive: true,
|
||||
},
|
||||
image: Platform.select({
|
||||
ios: "trash",
|
||||
android: "ic_menu_delete",
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<MenuView
|
||||
style={styles.menu}
|
||||
title="User options"
|
||||
onPressAction={({ nativeEvent }) => {
|
||||
switch (nativeEvent.event) {
|
||||
case "edit":
|
||||
console.log("Edit User here");
|
||||
break;
|
||||
case "delete":
|
||||
setTimeout(() => setVisible(true), 300);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}}
|
||||
actions={menuOptions}
|
||||
shouldOpenOnLongPress={false}
|
||||
>
|
||||
<MenuDotsIcon />
|
||||
</MenuView>
|
||||
<Dialog
|
||||
visible={visible}
|
||||
containerStyle={styles.dialog}
|
||||
onDismiss={() => setVisible(false)}
|
||||
>
|
||||
<Text center style={styles.title}>
|
||||
Are you sure?
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 18,
|
||||
fontFamily: "PlusJakartaSans_700Bold",
|
||||
color: "#979797",
|
||||
marginBottom: 20,
|
||||
}}
|
||||
center
|
||||
>
|
||||
This action will delete the {user.firstName}'s profile.
|
||||
</Text>
|
||||
<View row right gap-8 marginT-15>
|
||||
<Button
|
||||
label="Cancel"
|
||||
onPress={() => {
|
||||
setVisible(false);
|
||||
}}
|
||||
style={styles.cancelBtn}
|
||||
color="#999999"
|
||||
labelStyle={{ fontFamily: "Poppins_500Medium", fontSize: 13.53 }}
|
||||
/>
|
||||
<Button
|
||||
label="Yes"
|
||||
onPress={handleDeleteUser}
|
||||
style={styles.confirmBtn}
|
||||
labelStyle={{ fontFamily: "PlusJakartaSans_500Medium" }}
|
||||
/>
|
||||
</View>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
menu: {
|
||||
padding: 5,
|
||||
},
|
||||
confirmBtn: {
|
||||
backgroundColor: "#ff1637",
|
||||
},
|
||||
cancelBtn: {
|
||||
backgroundColor: "white",
|
||||
},
|
||||
dialog: {
|
||||
backgroundColor: "white",
|
||||
paddingHorizontal: 25,
|
||||
paddingTop: 25,
|
||||
paddingBottom: 17,
|
||||
borderRadius: 20,
|
||||
},
|
||||
title: {
|
||||
fontFamily: "Manrope_600SemiBold",
|
||||
fontSize: 22,
|
||||
marginBottom: 5,
|
||||
},
|
||||
text: {
|
||||
fontFamily: "PlusJakartaSans_400Regular",
|
||||
fontSize: 16,
|
||||
marginBottom: 0,
|
||||
},
|
||||
});
|
||||
|
||||
export default UserOptions;
|
@ -162,6 +162,87 @@ exports.createSubUser = onRequest(async (request, response) => {
|
||||
}
|
||||
});
|
||||
|
||||
exports.removeSubUser = onRequest(async (request, response) => {
|
||||
const authHeader = request.get('Authorization');
|
||||
|
||||
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||
logger.warn("Missing or incorrect Authorization header", {authHeader});
|
||||
response.status(401).json({error: 'Unauthorized'});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const token = authHeader.split('Bearer ')[1];
|
||||
logger.info("Verifying ID token", {token});
|
||||
|
||||
let decodedToken;
|
||||
try {
|
||||
decodedToken = await getAuth().verifyIdToken(token);
|
||||
logger.info("ID token verified successfully", {uid: decodedToken.uid});
|
||||
} catch (verifyError) {
|
||||
logger.error("ID token verification failed", {error: verifyError.message});
|
||||
response.status(401).json({error: 'Unauthorized: Invalid token'});
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("Processing user removal", {requestBody: request.body.data});
|
||||
|
||||
const { userId, familyId } = request.body.data;
|
||||
|
||||
if (!userId || !familyId) {
|
||||
logger.warn("Missing required fields in request body", {requestBody: request.body.data});
|
||||
response.status(400).json({error: "Missing required fields"});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const userProfile = await getFirestore()
|
||||
.collection("Profiles")
|
||||
.doc(userId)
|
||||
.get();
|
||||
|
||||
if (!userProfile.exists) {
|
||||
logger.error("User profile not found", {userId});
|
||||
response.status(404).json({error: "User not found"});
|
||||
return;
|
||||
}
|
||||
|
||||
if (userProfile.data().familyId !== familyId) {
|
||||
logger.error("User does not belong to the specified family", {
|
||||
userId,
|
||||
requestedFamilyId: familyId,
|
||||
actualFamilyId: userProfile.data().familyId
|
||||
});
|
||||
response.status(403).json({error: "User does not belong to the specified family"});
|
||||
return;
|
||||
}
|
||||
|
||||
await getFirestore()
|
||||
.collection("Profiles")
|
||||
.doc(userId)
|
||||
.delete();
|
||||
logger.info("User profile deleted from Firestore", {userId});
|
||||
|
||||
await getAuth().deleteUser(userId);
|
||||
logger.info("User authentication deleted", {userId});
|
||||
|
||||
response.status(200).json({
|
||||
data: {
|
||||
message: "User removed successfully",
|
||||
success: true
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Failed to remove user", {error: error.message});
|
||||
response.status(500).json({error: "Failed to remove user"});
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error("Error in removeSubUser function", {error: error.message});
|
||||
response.status(500).json({data: {error: error.message}});
|
||||
}
|
||||
});
|
||||
|
||||
exports.generateCustomToken = onRequest(async (request, response) => {
|
||||
try {
|
||||
const {userId} = request.body.data;
|
||||
|
27
hooks/firebase/useRemoveSubUser.ts
Normal file
27
hooks/firebase/useRemoveSubUser.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { useMutation, useQueryClient } from "react-query";
|
||||
import functions from '@react-native-firebase/functions';
|
||||
import { ProfileType, useAuthContext } from "@/contexts/AuthContext";
|
||||
import { HttpsCallableResult } from "@firebase/functions";
|
||||
|
||||
export const useRemoveSubUser = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { profileType, profileData } = useAuthContext();
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["removeSubUser"],
|
||||
mutationFn: async (userId: string) => {
|
||||
if (profileType === ProfileType.PARENT) {
|
||||
return await functions().httpsCallable("removeSubUser")({
|
||||
userId,
|
||||
familyId: profileData?.familyId!,
|
||||
}) as HttpsCallableResult<{ success: boolean }>;
|
||||
} else {
|
||||
throw Error("Can't remove sub-users as a non-parent.");
|
||||
}
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["getChildrenByParentId"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["familyMembers"] });
|
||||
},
|
||||
});
|
||||
};
|
@ -40,6 +40,7 @@
|
||||
"@react-native-firebase/firestore": "^20.4.0",
|
||||
"@react-native-firebase/functions": "^20.4.0",
|
||||
"@react-native-firebase/storage": "^21.0.0",
|
||||
"@react-native-menu/menu": "^1.1.6",
|
||||
"@react-navigation/drawer": "^6.7.2",
|
||||
"@react-navigation/native": "^6.0.2",
|
||||
"date-fns": "^3.6.0",
|
||||
|
@ -2573,6 +2573,11 @@
|
||||
resolved "https://registry.npmjs.org/@react-native-firebase/storage/-/storage-21.0.0.tgz"
|
||||
integrity sha512-meft5Pu0nI7zxhpnP49ko9Uw8GaIy9hXGJfa/fCFrpf2vA9OXdTr3CvgloH/b9DpbkwQGcGTshRqltuttXI67w==
|
||||
|
||||
"@react-native-menu/menu@^1.1.6":
|
||||
version "1.1.6"
|
||||
resolved "https://registry.yarnpkg.com/@react-native-menu/menu/-/menu-1.1.6.tgz#df6b4bf46a8ac5718605203f7fcd6fd3684715f5"
|
||||
integrity sha512-KRPBqa9jmYDFoacUxw8z1ucpbvmdlPuRO8tsFt2jM8JMC2s+YQwTtISG73PeqH9KD7BV+8igD/nizPfcipOmhQ==
|
||||
|
||||
"@react-native/assets-registry@0.74.85":
|
||||
version "0.74.85"
|
||||
resolved "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.74.85.tgz"
|
||||
|
Reference in New Issue
Block a user