add more options to user management

This commit is contained in:
ivic00
2024-11-12 21:38:03 +01:00
parent e2aae47c34
commit 3fb9dd0035
8 changed files with 868 additions and 577 deletions

View 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;

View File

@ -98,7 +98,6 @@ const DeleteProfileDialogs: React.FC<ConfirmationDialogProps> = ({
); );
}; };
// Empty stylesheet for future styles
const styles = StyleSheet.create({ const styles = StyleSheet.create({
confirmBtn: { confirmBtn: {
backgroundColor: "#ff1637", backgroundColor: "#ff1637",

File diff suppressed because it is too large Load Diff

View 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;

View File

@ -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) => { exports.generateCustomToken = onRequest(async (request, response) => {
try { try {
const {userId} = request.body.data; const {userId} = request.body.data;

View 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"] });
},
});
};

View File

@ -40,6 +40,7 @@
"@react-native-firebase/firestore": "^20.4.0", "@react-native-firebase/firestore": "^20.4.0",
"@react-native-firebase/functions": "^20.4.0", "@react-native-firebase/functions": "^20.4.0",
"@react-native-firebase/storage": "^21.0.0", "@react-native-firebase/storage": "^21.0.0",
"@react-native-menu/menu": "^1.1.6",
"@react-navigation/drawer": "^6.7.2", "@react-navigation/drawer": "^6.7.2",
"@react-navigation/native": "^6.0.2", "@react-navigation/native": "^6.0.2",
"date-fns": "^3.6.0", "date-fns": "^3.6.0",

View File

@ -2573,6 +2573,11 @@
resolved "https://registry.npmjs.org/@react-native-firebase/storage/-/storage-21.0.0.tgz" resolved "https://registry.npmjs.org/@react-native-firebase/storage/-/storage-21.0.0.tgz"
integrity sha512-meft5Pu0nI7zxhpnP49ko9Uw8GaIy9hXGJfa/fCFrpf2vA9OXdTr3CvgloH/b9DpbkwQGcGTshRqltuttXI67w== 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": "@react-native/assets-registry@0.74.85":
version "0.74.85" version "0.74.85"
resolved "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.74.85.tgz" resolved "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.74.85.tgz"