diff --git a/components/pages/settings/user_settings_views/MyGroup.tsx b/components/pages/settings/user_settings_views/MyGroup.tsx
index 11fcc3c..f6ce778 100644
--- a/components/pages/settings/user_settings_views/MyGroup.tsx
+++ b/components/pages/settings/user_settings_views/MyGroup.tsx
@@ -1,322 +1,388 @@
import {
- Avatar,
- Button,
- Card,
- Colors,
- Dialog,
- FloatingButton,
- PanningProvider,
- Picker,
- Text,
- TextField,
- TouchableOpacity,
- View,
+ Avatar,
+ Button,
+ Card,
+ Colors,
+ Dialog,
+ FloatingButton,
+ PanningProvider,
+ Picker,
+ Text,
+ TextField,
+ TouchableOpacity,
+ View,
} from "react-native-ui-lib";
-import React, { useState } from "react";
-import { ScrollView, StyleSheet } from "react-native";
-import { PickerSingleValue } from "react-native-ui-lib/src/components/picker/types";
-import { useCreateSubUser } from "@/hooks/firebase/useCreateSubUser";
-import { ProfileType } from "@/contexts/AuthContext";
-import { useGetFamilyMembers } from "@/hooks/firebase/useGetFamilyMembers";
+import React, {useState} from "react";
+import {ScrollView, StyleSheet} from "react-native";
+import {PickerSingleValue} from "react-native-ui-lib/src/components/picker/types";
+import {useCreateSubUser} from "@/hooks/firebase/useCreateSubUser";
+import {ProfileType} from "@/contexts/AuthContext";
+import {useGetFamilyMembers} from "@/hooks/firebase/useGetFamilyMembers";
import UserMenu from "@/components/pages/settings/user_settings_views/UserMenu";
+import {uuidv4} from "@firebase/util";
const MyGroup = () => {
- const [showAddUserDialog, setShowAddUserDialog] = useState(false);
- const [showNewUserInfoDialog, setShowNewUserInfoDialog] = useState(false);
- const [selectedStatus, setSelectedStatus] = useState<
- string | PickerSingleValue
- >(ProfileType.CHILD);
- const [firstName, setFirstName] = useState("");
- const [lastName, setLastName] = useState("");
- const [email, setEmail] = useState("");
+ const [showAddUserDialog, setShowAddUserDialog] = useState(false);
+ const [showNewUserInfoDialog, setShowNewUserInfoDialog] = useState(false);
+ const [selectedStatus, setSelectedStatus] = useState<
+ string | PickerSingleValue
+ >(ProfileType.CHILD);
+ const [firstName, setFirstName] = useState("");
+ const [lastName, setLastName] = useState("");
+ const [email, setEmail] = useState("");
- const { mutateAsync: createSubUser, isLoading, isError } = useCreateSubUser();
- const { data: familyMembers } = useGetFamilyMembers(true);
+ const [showQRCodeDialog, setShowQRCodeDialog] = useState("");
- const parents =
- familyMembers?.filter((x) => x.userType === ProfileType.PARENT) ?? [];
- const children =
- familyMembers?.filter((x) => x.userType === ProfileType.CHILD) ?? [];
- const caregivers =
- familyMembers?.filter((x) => x.userType === ProfileType.CAREGIVER) ?? [];
+ const {mutateAsync: createSubUser, isLoading, isError} = useCreateSubUser();
+ const {data: familyMembers} = useGetFamilyMembers(true);
- const handleCreateSubUser = async () => {
- if (!firstName || !lastName || !email) {
- console.error("All fields are required");
- return;
- }
+ const parents =
+ familyMembers?.filter((x) => x.userType === ProfileType.PARENT) ?? [];
+ const children =
+ familyMembers?.filter((x) => x.userType === ProfileType.CHILD) ?? [];
+ const caregivers =
+ familyMembers?.filter((x) => x.userType === ProfileType.CAREGIVER) ?? [];
+ const familyDevices =
+ familyMembers?.filter((x) => x.userType === ProfileType.FAMILY_DEVICE) ?? [];
- if (!email.includes("@")) {
- console.error("Invalid email address");
- return;
- }
+ const handleCreateSubUser = async () => {
+ if (!firstName || (selectedStatus !== ProfileType.FAMILY_DEVICE && !lastName)) {
+ console.error("First name and last name are required");
+ return;
+ }
- await createSubUser({
- firstName,
- lastName,
- email,
- password: email,
- userType: selectedStatus as ProfileType,
- });
+ if (selectedStatus !== ProfileType.FAMILY_DEVICE && !email) {
+ console.error("Email is required for non-family device users");
+ return;
+ }
- if (!isError) {
- setShowNewUserInfoDialog(false);
- }
- };
+ if (email && !email.includes("@")) {
+ console.error("Invalid email address");
+ return;
+ }
- console.log(familyMembers);
+ const res = await createSubUser({
+ firstName,
+ lastName: selectedStatus === ProfileType.FAMILY_DEVICE ? "" : lastName,
+ email: email || `placeholder_${uuidv4().split("-")[0]}@family.device`,
+ password: uuidv4(),
+ userType: selectedStatus as ProfileType,
+ });
+ console.log(res)
- return (
-
-
-
- {!parents.length && !children.length && !caregivers.length && (
-
- {isLoading ? "Loading...." : "No user devices added"}
-
- )}
+ if (!isError) {
+ setShowNewUserInfoDialog(false);
- {(!!parents.length || !!children.length) && (
- <>
-
- Family
-
- {[...parents, ...children]?.map((member) => (
-
-
-
-
- {member.firstName} {member.lastName}
-
-
- {member.userType === ProfileType.PARENT
- ? "Admin (You)"
- : "Child"}
-
-
+ if(res?.data?.userId) {
+ setTimeout(() => {
+ setShowQRCodeDialog(res.data.userId)
+ }, 500)
+ }
+ }
-
-
-
- ))}
- >
- )}
+ };
- {!!caregivers.length && (
- <>
-
- Caregivers
-
- {caregivers?.map((member) => (
-
-
-
-
- {member.firstName} {member.lastName}
-
-
- Caregiver
-
-
-
- ))}
- >
- )}
-
-
+ return (
+
+
+
+ {!parents.length && !children.length && !caregivers.length && (
+
+ {isLoading ? "Loading...." : "No user devices added"}
+
+ )}
- setShowAddUserDialog(true),
- }}
- />
+ {(!!parents.length || !!children.length) && (
+ <>
+
+ Family
+
+ {[...parents, ...children]?.map((member, index) => (
+
+
+
+
+ {member.firstName} {member.lastName}
+
+
+ {member.userType === ProfileType.PARENT
+ ? "Admin (You)"
+ : "Child"}
+
+
-
-
+ {!!caregivers.length && (
+ <>
+
+ Caregivers
+
+ {caregivers?.map((member) => (
+
+
+
+
+ {member.firstName} {member.lastName}
+
+
+ Caregiver
+
+
-
+
+
+ setShowAddUserDialog(true),
+ }}
/>
- {}}>
-
- Upload User Profile Photo
-
-
-
- Member Status
- setSelectedStatus(item)}
- style={styles.picker}
- showSearch
- floatingPlaceholder
- >
-
-
-
-
+
- Email Address
-
+
-
- );
+
+
+ {
+ }}>
+
+ Upload User Profile Photo
+
+
+
+
+ Member Status
+ setSelectedStatus(item)}
+ style={styles.picker}
+ showSearch
+ floatingPlaceholder
+ >
+
+
+
+
+
+
+
+ {selectedStatus === ProfileType.FAMILY_DEVICE ? "Device Name" : "First Name"}
+
+
+
+ {selectedStatus !== ProfileType.FAMILY_DEVICE && (
+ <>
+ Last Name
+
+ >
+ )}
+
+ {selectedStatus !== ProfileType.FAMILY_DEVICE && (
+ <>
+ Email Address (Optional)
+
+ >
+ )}
+
+
+
+
+
+ );
};
const styles = StyleSheet.create({
- card: {
- marginVertical: 15,
- backgroundColor: "white",
- width: "100%",
- borderRadius: 15,
- padding: 20,
- },
- familyCard: {
- marginBottom: 10,
- borderRadius: 10,
- backgroundColor: Colors.white,
- width: "100%",
- },
- inputField: {
- borderRadius: 50,
- paddingVertical: 12,
- paddingHorizontal: 16,
- backgroundColor: Colors.grey80,
- marginBottom: 16,
- borderColor: Colors.grey50,
- borderWidth: 1,
- height: 40,
- },
- picker: {
- borderRadius: 50,
- paddingVertical: 12,
- paddingHorizontal: 16,
- backgroundColor: Colors.grey80,
- marginBottom: 16,
- borderColor: Colors.grey50,
- borderWidth: 1,
- marginTop: -20,
- height: 40,
- },
- label: {
- marginBottom: 5,
- fontSize: 12,
- color: Colors.grey40,
- },
- dialogCard: {
- borderRadius: 10,
- gap: 10,
- },
+ card: {
+ marginVertical: 15,
+ backgroundColor: "white",
+ width: "100%",
+ borderRadius: 15,
+ padding: 20,
+ },
+ familyCard: {
+ marginBottom: 10,
+ borderRadius: 10,
+ backgroundColor: Colors.white,
+ width: "100%",
+ },
+ inputField: {
+ borderRadius: 50,
+ paddingVertical: 12,
+ paddingHorizontal: 16,
+ backgroundColor: Colors.grey80,
+ marginBottom: 16,
+ borderColor: Colors.grey50,
+ borderWidth: 1,
+ height: 40,
+ },
+ picker: {
+ borderRadius: 50,
+ paddingVertical: 12,
+ paddingHorizontal: 16,
+ backgroundColor: Colors.grey80,
+ marginBottom: 16,
+ borderColor: Colors.grey50,
+ borderWidth: 1,
+ marginTop: -20,
+ height: 40,
+ },
+ label: {
+ marginBottom: 5,
+ fontSize: 12,
+ color: Colors.grey40,
+ },
+ dialogCard: {
+ borderRadius: 10,
+ gap: 10,
+ },
});
export default MyGroup;
diff --git a/components/pages/settings/user_settings_views/MyProfile.tsx b/components/pages/settings/user_settings_views/MyProfile.tsx
index eb2f01b..d939d46 100644
--- a/components/pages/settings/user_settings_views/MyProfile.tsx
+++ b/components/pages/settings/user_settings_views/MyProfile.tsx
@@ -1,102 +1,102 @@
-import { View, Text, TextField } from "react-native-ui-lib";
-import React, { useState } from "react";
-import { StyleSheet } from "react-native";
-import { ScrollView } from "react-native-gesture-handler";
-import { useAuthContext } from "@/contexts/AuthContext";
-import { useSettingsContext } from "@/contexts/SettingsContext";
-import { useUpdateUserData } from "@/hooks/firebase/useUpdateUserData";
+import {Text, TextField, View} from "react-native-ui-lib";
+import React, {useState} from "react";
+import {StyleSheet} from "react-native";
+import {ScrollView} from "react-native-gesture-handler";
+import {useAuthContext} from "@/contexts/AuthContext";
+import {useUpdateUserData} from "@/hooks/firebase/useUpdateUserData";
+
const MyProfile = () => {
- const { user, profileData } = useAuthContext();
+ const {user, profileData} = useAuthContext();
- const [lastName, setLastName] = useState(profileData?.lastName || "");
- const [firstName, setFirstName] = useState(
- profileData?.firstName || ""
- );
+ const [lastName, setLastName] = useState(profileData?.lastName || "");
+ const [firstName, setFirstName] = useState(
+ profileData?.firstName || ""
+ );
- const { mutateAsync: updateUserData } = useUpdateUserData();
- return (
-
-
- Your Profile
-
-
-
- Change Photo
-
- Remove Photo
-
-
-
- First name
-
- {
- setFirstName(value);
- await updateUserData({ newUserData: { firstName: value } });
- }}
- />
-
- Last name
-
- {
- setLastName(value);
- await updateUserData({ newUserData: { lastName: value } });
- }}
- />
-
- Email address
-
-
-
-
+ const {mutateAsync: updateUserData} = useUpdateUserData();
+ return (
+
+
+ Your Profile
+
+
+
+ Change Photo
+
+ Remove Photo
+
+
+
+ First name
+
+ {
+ setFirstName(value);
+ await updateUserData({newUserData: {firstName: value}});
+ }}
+ />
+
+ Last name
+
+ {
+ setLastName(value);
+ await updateUserData({newUserData: {lastName: value}});
+ }}
+ />
+
+ Email address
+
+
+
+
-
- Settings
-
- Time Zone
-
-
-
-
- );
+
+ Settings
+
+ Time Zone
+
+
+
+
+ );
};
const styles = StyleSheet.create({
- card: {
- marginVertical: 15,
- backgroundColor: "white",
- width: "100%",
- borderRadius: 15,
- padding: 20,
- },
- pfp: {
- aspectRatio: 1,
- width: 60,
- backgroundColor: "green",
- borderRadius: 20,
- },
- txtBox: {
- backgroundColor: "#fafafa",
- borderRadius: 50,
- borderWidth: 2,
- borderColor: "#cecece",
- padding: 15,
- height: 45,
- },
+ card: {
+ marginVertical: 15,
+ backgroundColor: "white",
+ width: "100%",
+ borderRadius: 15,
+ padding: 20,
+ },
+ pfp: {
+ aspectRatio: 1,
+ width: 60,
+ backgroundColor: "green",
+ borderRadius: 20,
+ },
+ txtBox: {
+ backgroundColor: "#fafafa",
+ borderRadius: 50,
+ borderWidth: 2,
+ borderColor: "#cecece",
+ padding: 15,
+ height: 45,
+ },
});
export default MyProfile;
diff --git a/components/pages/settings/user_settings_views/UserMenu.tsx b/components/pages/settings/user_settings_views/UserMenu.tsx
index ce35997..ed01179 100644
--- a/components/pages/settings/user_settings_views/UserMenu.tsx
+++ b/components/pages/settings/user_settings_views/UserMenu.tsx
@@ -3,15 +3,22 @@ import {Button, Card, Colors, Dialog, Hint, ListItem, Text, View} from 'react-na
import QRCode from 'react-native-qrcode-svg';
import {PanningDirectionsEnum} from "react-native-ui-lib/src/components/panningViews/panningProvider";
-const UserMenu = ({userId}:{userId: string}) => {
+const UserMenu = ({
+ userId,
+ showQRCodeDialog,
+ setShowQRCodeDialog
+ }: {
+ userId: string,
+ showQRCodeDialog: boolean,
+ setShowQRCodeDialog: (value: boolean) => void
+}) => {
const [showHint, setShowHint] = useState(false);
- const [showQRCodeDialog, setShowQRCodeDialog] = useState(false);
const handleShowQRCode = () => {
setShowHint(false);
setTimeout(() => {
setShowQRCodeDialog(true);
- }, 500)
+ }, 500);
};
return (
@@ -22,9 +29,7 @@ const UserMenu = ({userId}:{userId: string}) => {
color={Colors.white}
customContent={
-
+
Show Login QR Code
@@ -34,7 +39,7 @@ const UserMenu = ({userId}:{userId: string}) => {
backdropColor="transparent"
>
-
@@ -47,7 +52,7 @@ const UserMenu = ({userId}:{userId: string}) => {
>
Scan this QR Code to Login:
-
+
= ({children}) =>
const {replace} = useRouter();
const ready = !initializing;
+ const queryClient = useQueryClient();
+
const onAuthStateChangedHandler = async (authUser: FirebaseAuthTypes.User | null) => {
setUser(authUser);
@@ -153,6 +158,17 @@ export const AuthContextProvider: FC<{ children: ReactNode }> = ({children}) =>
}
}, [user, ready]);
+ useEffect(() => {
+ const sub = Notifications.addNotificationReceivedListener(notification => {
+ const eventId = notification?.request?.content?.data?.eventId;
+
+ if (eventId) {
+ queryClient.invalidateQueries(['events']);
+ }
+ });
+ return () => sub.remove()
+ }, []);
+
if (!ready) {
return null;
}
diff --git a/firebase/functions/index.js b/firebase/functions/index.js
index 37e0c53..4827235 100644
--- a/firebase/functions/index.js
+++ b/firebase/functions/index.js
@@ -132,7 +132,7 @@ exports.createSubUser = onRequest(async (request, response) => {
const {userType, firstName, lastName, email, password, familyId} = request.body.data;
- if (!email || !password || !firstName || !lastName || !userType || !familyId) {
+ if (!email || !password || !firstName || !userType || !familyId) {
logger.warn("Missing required fields in request body", {requestBody: request.body.data});
response.status(400).json({error: "Missing required fields"});
return;
diff --git a/hooks/firebase/useCreateSubUser.ts b/hooks/firebase/useCreateSubUser.ts
index 3761713..84c5408 100644
--- a/hooks/firebase/useCreateSubUser.ts
+++ b/hooks/firebase/useCreateSubUser.ts
@@ -2,6 +2,7 @@ import {useMutation, useQueryClient} from "react-query";
import {UserProfile} from "@/hooks/firebase/types/profileTypes";
import functions from '@react-native-firebase/functions';
import {ProfileType, useAuthContext} from "@/contexts/AuthContext";
+import {HttpsCallableResult} from "@firebase/functions";
export const useCreateSubUser = () => {
const queryClient = useQueryClient()
@@ -9,13 +10,13 @@ export const useCreateSubUser = () => {
return useMutation({
mutationKey: ["createSubUser"],
- mutationFn: async ({email, ...userProfile}: { email: string } & UserProfile) => {
+ mutationFn: async ({email, ...userProfile}: { email?: string } & UserProfile) => {
if (profileType === ProfileType.PARENT) {
return await functions().httpsCallable("createSubUser")({
...userProfile,
email,
familyId: profileData?.familyId
- })
+ }) as HttpsCallableResult<{ userId: string }>
} else {
throw Error("Can't create sub-users as a non-parent.")
}