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",
|
||||
|
File diff suppressed because it is too large
Load Diff
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