mirror of
https://github.com/urosran/cally.git
synced 2025-07-14 17:25:46 +00:00
Added creating family devices, refetch calendar on notification received
This commit is contained in:
@ -1,322 +1,388 @@
|
|||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
Colors,
|
Colors,
|
||||||
Dialog,
|
Dialog,
|
||||||
FloatingButton,
|
FloatingButton,
|
||||||
PanningProvider,
|
PanningProvider,
|
||||||
Picker,
|
Picker,
|
||||||
Text,
|
Text,
|
||||||
TextField,
|
TextField,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
View,
|
View,
|
||||||
} from "react-native-ui-lib";
|
} from "react-native-ui-lib";
|
||||||
import React, { useState } from "react";
|
import React, {useState} from "react";
|
||||||
import { ScrollView, StyleSheet } from "react-native";
|
import {ScrollView, StyleSheet} from "react-native";
|
||||||
import { PickerSingleValue } from "react-native-ui-lib/src/components/picker/types";
|
import {PickerSingleValue} from "react-native-ui-lib/src/components/picker/types";
|
||||||
import { useCreateSubUser } from "@/hooks/firebase/useCreateSubUser";
|
import {useCreateSubUser} from "@/hooks/firebase/useCreateSubUser";
|
||||||
import { ProfileType } from "@/contexts/AuthContext";
|
import {ProfileType} from "@/contexts/AuthContext";
|
||||||
import { useGetFamilyMembers } from "@/hooks/firebase/useGetFamilyMembers";
|
import {useGetFamilyMembers} from "@/hooks/firebase/useGetFamilyMembers";
|
||||||
import UserMenu from "@/components/pages/settings/user_settings_views/UserMenu";
|
import UserMenu from "@/components/pages/settings/user_settings_views/UserMenu";
|
||||||
|
import {uuidv4} from "@firebase/util";
|
||||||
|
|
||||||
const MyGroup = () => {
|
const MyGroup = () => {
|
||||||
const [showAddUserDialog, setShowAddUserDialog] = useState(false);
|
const [showAddUserDialog, setShowAddUserDialog] = useState(false);
|
||||||
const [showNewUserInfoDialog, setShowNewUserInfoDialog] = useState(false);
|
const [showNewUserInfoDialog, setShowNewUserInfoDialog] = useState(false);
|
||||||
const [selectedStatus, setSelectedStatus] = useState<
|
const [selectedStatus, setSelectedStatus] = useState<
|
||||||
string | PickerSingleValue
|
string | PickerSingleValue
|
||||||
>(ProfileType.CHILD);
|
>(ProfileType.CHILD);
|
||||||
const [firstName, setFirstName] = useState("");
|
const [firstName, setFirstName] = useState("");
|
||||||
const [lastName, setLastName] = useState("");
|
const [lastName, setLastName] = useState("");
|
||||||
const [email, setEmail] = useState("");
|
const [email, setEmail] = useState("");
|
||||||
|
|
||||||
const { mutateAsync: createSubUser, isLoading, isError } = useCreateSubUser();
|
const [showQRCodeDialog, setShowQRCodeDialog] = useState("");
|
||||||
const { data: familyMembers } = useGetFamilyMembers(true);
|
|
||||||
|
|
||||||
const parents =
|
const {mutateAsync: createSubUser, isLoading, isError} = useCreateSubUser();
|
||||||
familyMembers?.filter((x) => x.userType === ProfileType.PARENT) ?? [];
|
const {data: familyMembers} = useGetFamilyMembers(true);
|
||||||
const children =
|
|
||||||
familyMembers?.filter((x) => x.userType === ProfileType.CHILD) ?? [];
|
|
||||||
const caregivers =
|
|
||||||
familyMembers?.filter((x) => x.userType === ProfileType.CAREGIVER) ?? [];
|
|
||||||
|
|
||||||
const handleCreateSubUser = async () => {
|
const parents =
|
||||||
if (!firstName || !lastName || !email) {
|
familyMembers?.filter((x) => x.userType === ProfileType.PARENT) ?? [];
|
||||||
console.error("All fields are required");
|
const children =
|
||||||
return;
|
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("@")) {
|
const handleCreateSubUser = async () => {
|
||||||
console.error("Invalid email address");
|
if (!firstName || (selectedStatus !== ProfileType.FAMILY_DEVICE && !lastName)) {
|
||||||
return;
|
console.error("First name and last name are required");
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await createSubUser({
|
if (selectedStatus !== ProfileType.FAMILY_DEVICE && !email) {
|
||||||
firstName,
|
console.error("Email is required for non-family device users");
|
||||||
lastName,
|
return;
|
||||||
email,
|
}
|
||||||
password: email,
|
|
||||||
userType: selectedStatus as ProfileType,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!isError) {
|
if (email && !email.includes("@")) {
|
||||||
setShowNewUserInfoDialog(false);
|
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 (
|
if (!isError) {
|
||||||
<View style={{ flex: 1 }}>
|
setShowNewUserInfoDialog(false);
|
||||||
<View>
|
|
||||||
<ScrollView style={styles.card}>
|
|
||||||
{!parents.length && !children.length && !caregivers.length && (
|
|
||||||
<Text text70 marginV-10>
|
|
||||||
{isLoading ? "Loading...." : "No user devices added"}
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{(!!parents.length || !!children.length) && (
|
if(res?.data?.userId) {
|
||||||
<>
|
setTimeout(() => {
|
||||||
<Text text70 marginV-10>
|
setShowQRCodeDialog(res.data.userId)
|
||||||
Family
|
}, 500)
|
||||||
</Text>
|
}
|
||||||
{[...parents, ...children]?.map((member) => (
|
}
|
||||||
<Card
|
|
||||||
enableShadow={false}
|
|
||||||
elevation={0}
|
|
||||||
key={`${member.firstName}_${member.lastName}`}
|
|
||||||
style={styles.familyCard}
|
|
||||||
row
|
|
||||||
centerV
|
|
||||||
padding-10
|
|
||||||
>
|
|
||||||
<Avatar
|
|
||||||
source={{ uri: "https://via.placeholder.com/60" }}
|
|
||||||
size={40}
|
|
||||||
backgroundColor={Colors.grey60}
|
|
||||||
/>
|
|
||||||
<View marginL-10>
|
|
||||||
<Text text70M>
|
|
||||||
{member.firstName} {member.lastName}
|
|
||||||
</Text>
|
|
||||||
<Text text90 grey40>
|
|
||||||
{member.userType === ProfileType.PARENT
|
|
||||||
? "Admin (You)"
|
|
||||||
: "Child"}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View flex-1 />
|
|
||||||
|
|
||||||
<UserMenu userId={member?.uid!} />
|
};
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!!caregivers.length && (
|
return (
|
||||||
<>
|
<View style={{flex: 1}}>
|
||||||
<Text text70 marginB-10 marginT-15>
|
<View>
|
||||||
Caregivers
|
<ScrollView style={styles.card}>
|
||||||
</Text>
|
{!parents.length && !children.length && !caregivers.length && (
|
||||||
{caregivers?.map((member) => (
|
<Text text70 marginV-10>
|
||||||
<Card
|
{isLoading ? "Loading...." : "No user devices added"}
|
||||||
enableShadow={false}
|
</Text>
|
||||||
elevation={0}
|
)}
|
||||||
key={`${member.firstName}_${member.lastName}`}
|
|
||||||
style={styles.familyCard}
|
|
||||||
row
|
|
||||||
centerV
|
|
||||||
padding-10
|
|
||||||
>
|
|
||||||
<Avatar
|
|
||||||
source={{ uri: "https://via.placeholder.com/60" }}
|
|
||||||
size={40}
|
|
||||||
backgroundColor={Colors.grey60}
|
|
||||||
/>
|
|
||||||
<View marginL-10>
|
|
||||||
<Text text70M>
|
|
||||||
{member.firstName} {member.lastName}
|
|
||||||
</Text>
|
|
||||||
<Text text90 grey40>
|
|
||||||
Caregiver
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</ScrollView>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<FloatingButton
|
{(!!parents.length || !!children.length) && (
|
||||||
fullWidth
|
<>
|
||||||
hideBackgroundOverlay
|
<Text text70 marginV-10>
|
||||||
visible
|
Family
|
||||||
button={{
|
</Text>
|
||||||
label: "+ Add a user device",
|
{[...parents, ...children]?.map((member, index) => (
|
||||||
onPress: () => setShowAddUserDialog(true),
|
<Card
|
||||||
}}
|
enableShadow={false}
|
||||||
/>
|
elevation={0}
|
||||||
|
key={`${member.firstName}_${member.lastName}_${index}`}
|
||||||
|
style={styles.familyCard}
|
||||||
|
row
|
||||||
|
centerV
|
||||||
|
padding-10
|
||||||
|
>
|
||||||
|
<Avatar
|
||||||
|
source={{uri: "https://via.placeholder.com/60"}}
|
||||||
|
size={40}
|
||||||
|
backgroundColor={Colors.grey60}
|
||||||
|
/>
|
||||||
|
<View marginL-10>
|
||||||
|
<Text text70M>
|
||||||
|
{member.firstName} {member.lastName}
|
||||||
|
</Text>
|
||||||
|
<Text text90 grey40>
|
||||||
|
{member.userType === ProfileType.PARENT
|
||||||
|
? "Admin (You)"
|
||||||
|
: "Child"}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
<Dialog
|
<View flex-1/>
|
||||||
visible={showAddUserDialog}
|
|
||||||
onDismiss={() => setShowAddUserDialog(false)}
|
|
||||||
panDirection={PanningProvider.Directions.DOWN}
|
|
||||||
>
|
|
||||||
<Card padding-25 gap-10>
|
|
||||||
<Text>Add a new user device</Text>
|
|
||||||
|
|
||||||
<Button backgroundColor={"#FD1775"}>
|
<UserMenu setShowQRCodeDialog={(val) => setShowQRCodeDialog("")} showQRCodeDialog={showQRCodeDialog === member?.uid} userId={member?.uid!}/>
|
||||||
<Text white>Show a QR Code</Text>
|
</Card>
|
||||||
</Button>
|
))}
|
||||||
<Button
|
</>
|
||||||
backgroundColor={"#05A8B6"}
|
)}
|
||||||
onPress={() => {
|
|
||||||
setShowAddUserDialog(false);
|
|
||||||
setTimeout(() => {
|
|
||||||
setShowNewUserInfoDialog(true);
|
|
||||||
}, 500);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Text white>Enter email address</Text>
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<TouchableOpacity onPress={() => setShowAddUserDialog(false)} center>
|
{!!caregivers.length && (
|
||||||
<Text>Return to user settings</Text>
|
<>
|
||||||
</TouchableOpacity>
|
<Text text70 marginB-10 marginT-15>
|
||||||
</Card>
|
Caregivers
|
||||||
</Dialog>
|
</Text>
|
||||||
|
{caregivers?.map((member) => (
|
||||||
|
<Card
|
||||||
|
enableShadow={false}
|
||||||
|
elevation={0}
|
||||||
|
key={`${member.firstName}_${member.lastName}`}
|
||||||
|
style={styles.familyCard}
|
||||||
|
row
|
||||||
|
centerV
|
||||||
|
padding-10
|
||||||
|
>
|
||||||
|
<Avatar
|
||||||
|
source={{uri: "https://via.placeholder.com/60"}}
|
||||||
|
size={40}
|
||||||
|
backgroundColor={Colors.grey60}
|
||||||
|
/>
|
||||||
|
<View marginL-10>
|
||||||
|
<Text text70M>
|
||||||
|
{member.firstName} {member.lastName}
|
||||||
|
</Text>
|
||||||
|
<Text text90 grey40>
|
||||||
|
Caregiver
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
<Dialog
|
<UserMenu setShowQRCodeDialog={(val) => setShowQRCodeDialog("")} showQRCodeDialog={showQRCodeDialog === member?.uid} userId={member?.uid!}/>
|
||||||
panDirection={PanningProvider.Directions.DOWN}
|
</Card>
|
||||||
visible={showNewUserInfoDialog}
|
))}
|
||||||
onDismiss={() => setShowNewUserInfoDialog(false)}
|
</>
|
||||||
>
|
)}
|
||||||
<Card padding-25 style={styles.dialogCard}>
|
|
||||||
<View row spread>
|
|
||||||
<Text text60M>New User Information</Text>
|
|
||||||
<TouchableOpacity onPress={() => setShowAddUserDialog(false)}>
|
|
||||||
<Text>X</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View row centerV gap-20 marginV-20>
|
{!!familyDevices.length && (
|
||||||
<Avatar
|
<>
|
||||||
imageStyle={{ borderRadius: 10 }}
|
<Text text70 marginB-10 marginT-15>
|
||||||
containerStyle={{ borderRadius: 10 }}
|
Family Devices
|
||||||
size={60}
|
</Text>
|
||||||
backgroundColor={Colors.grey60}
|
{familyDevices?.map((member, index) => (
|
||||||
|
<Card
|
||||||
|
enableShadow={false}
|
||||||
|
elevation={0}
|
||||||
|
key={`${member.firstName}_${member.lastName}_${index}`}
|
||||||
|
style={styles.familyCard}
|
||||||
|
row
|
||||||
|
centerV
|
||||||
|
padding-10
|
||||||
|
>
|
||||||
|
<Avatar
|
||||||
|
source={{uri: "https://via.placeholder.com/60"}}
|
||||||
|
size={40}
|
||||||
|
backgroundColor={Colors.grey60}
|
||||||
|
/>
|
||||||
|
<View marginL-10>
|
||||||
|
<Text text70M>
|
||||||
|
{member.firstName}
|
||||||
|
</Text>
|
||||||
|
<Text text90 grey40>
|
||||||
|
Family Device
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<UserMenu setShowQRCodeDialog={(val) => setShowQRCodeDialog("")} showQRCodeDialog={showQRCodeDialog === member?.uid} userId={member?.uid!}/>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<FloatingButton
|
||||||
|
fullWidth
|
||||||
|
hideBackgroundOverlay
|
||||||
|
visible
|
||||||
|
button={{
|
||||||
|
label: "+ Add a user device",
|
||||||
|
onPress: () => setShowAddUserDialog(true),
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<TouchableOpacity onPress={() => {}}>
|
|
||||||
<Text style={{ color: Colors.green10 }}>
|
|
||||||
Upload User Profile Photo
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<Text style={styles.label}>Member Status</Text>
|
<Dialog
|
||||||
<Picker
|
visible={showAddUserDialog}
|
||||||
editable={!isLoading}
|
onDismiss={() => setShowAddUserDialog(false)}
|
||||||
value={selectedStatus}
|
panDirection={PanningProvider.Directions.DOWN}
|
||||||
//@ts-ignore
|
>
|
||||||
onChange={(item) => setSelectedStatus(item)}
|
<Card padding-25 gap-10>
|
||||||
style={styles.picker}
|
<Text>Add a new user device</Text>
|
||||||
showSearch
|
|
||||||
floatingPlaceholder
|
|
||||||
>
|
|
||||||
<Picker.Item label="Child" value={ProfileType.CHILD} />
|
|
||||||
<Picker.Item label="Parent" value={ProfileType.PARENT} />
|
|
||||||
<Picker.Item label="Caregiver" value={ProfileType.CAREGIVER} />
|
|
||||||
</Picker>
|
|
||||||
|
|
||||||
<Text style={styles.label}>First Name</Text>
|
<Button backgroundColor={"#FD1775"}>
|
||||||
<TextField
|
<Text white>Show a QR Code</Text>
|
||||||
editable={!isLoading}
|
</Button>
|
||||||
placeholder="First name"
|
<Button
|
||||||
value={firstName}
|
backgroundColor={"#05A8B6"}
|
||||||
onChangeText={setFirstName}
|
onPress={() => {
|
||||||
style={styles.inputField}
|
setShowAddUserDialog(false);
|
||||||
/>
|
setTimeout(() => {
|
||||||
|
setShowNewUserInfoDialog(true);
|
||||||
|
}, 500);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text white>Enter email address</Text>
|
||||||
|
</Button>
|
||||||
|
|
||||||
<Text style={styles.label}>Last Name</Text>
|
<TouchableOpacity onPress={() => setShowAddUserDialog(false)} center>
|
||||||
<TextField
|
<Text>Return to user settings</Text>
|
||||||
editable={!isLoading}
|
</TouchableOpacity>
|
||||||
placeholder="Last name"
|
</Card>
|
||||||
value={lastName}
|
</Dialog>
|
||||||
onChangeText={setLastName}
|
|
||||||
style={styles.inputField}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Text style={styles.label}>Email Address</Text>
|
<Dialog
|
||||||
<TextField
|
panDirection={PanningProvider.Directions.DOWN}
|
||||||
editable={!isLoading}
|
visible={showNewUserInfoDialog}
|
||||||
placeholder="Email address"
|
onDismiss={() => setShowNewUserInfoDialog(false)}
|
||||||
value={email}
|
>
|
||||||
onChangeText={setEmail}
|
<Card padding-25 style={styles.dialogCard}>
|
||||||
keyboardType="email-address"
|
<View row spread>
|
||||||
autoCapitalize="none"
|
<Text text60M>New User Information</Text>
|
||||||
style={styles.inputField}
|
<TouchableOpacity onPress={() => setShowAddUserDialog(false)}>
|
||||||
/>
|
<Text>X</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
<Button
|
<View row centerV gap-20 marginV-20>
|
||||||
disabled={!firstName || !lastName || !email || isLoading}
|
<Avatar
|
||||||
label={isLoading ? "Adding..." : "Add group member"}
|
imageStyle={{borderRadius: 10}}
|
||||||
backgroundColor="#FD1775"
|
containerStyle={{borderRadius: 10}}
|
||||||
style={{ marginTop: 20 }}
|
size={60}
|
||||||
onPress={handleCreateSubUser}
|
backgroundColor={Colors.grey60}
|
||||||
/>
|
/>
|
||||||
</Card>
|
<TouchableOpacity onPress={() => {
|
||||||
</Dialog>
|
}}>
|
||||||
</View>
|
<Text style={{color: Colors.green10}}>
|
||||||
);
|
Upload User Profile Photo
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Text style={styles.label}>Member Status</Text>
|
||||||
|
<Picker
|
||||||
|
editable={!isLoading}
|
||||||
|
value={selectedStatus}
|
||||||
|
//@ts-ignore
|
||||||
|
onChange={(item) => setSelectedStatus(item)}
|
||||||
|
style={styles.picker}
|
||||||
|
showSearch
|
||||||
|
floatingPlaceholder
|
||||||
|
>
|
||||||
|
<Picker.Item label="Child" value={ProfileType.CHILD}/>
|
||||||
|
<Picker.Item label="Parent" value={ProfileType.PARENT}/>
|
||||||
|
<Picker.Item label="Caregiver" value={ProfileType.CAREGIVER}/>
|
||||||
|
<Picker.Item label="Family Device" value={ProfileType.FAMILY_DEVICE}/>
|
||||||
|
</Picker>
|
||||||
|
|
||||||
|
<Text style={styles.label}>
|
||||||
|
{selectedStatus === ProfileType.FAMILY_DEVICE ? "Device Name" : "First Name"}
|
||||||
|
</Text>
|
||||||
|
<TextField
|
||||||
|
editable={!isLoading}
|
||||||
|
placeholder={selectedStatus === ProfileType.FAMILY_DEVICE ? "Device name" : "First name"}
|
||||||
|
value={firstName}
|
||||||
|
onChangeText={setFirstName}
|
||||||
|
style={styles.inputField}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{selectedStatus !== ProfileType.FAMILY_DEVICE && (
|
||||||
|
<>
|
||||||
|
<Text style={styles.label}>Last Name</Text>
|
||||||
|
<TextField
|
||||||
|
editable={!isLoading}
|
||||||
|
placeholder="Last name"
|
||||||
|
value={lastName}
|
||||||
|
onChangeText={setLastName}
|
||||||
|
style={styles.inputField}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{selectedStatus !== ProfileType.FAMILY_DEVICE && (
|
||||||
|
<>
|
||||||
|
<Text style={styles.label}>Email Address (Optional)</Text>
|
||||||
|
<TextField
|
||||||
|
editable={!isLoading}
|
||||||
|
placeholder="Email address"
|
||||||
|
value={email}
|
||||||
|
onChangeText={setEmail}
|
||||||
|
keyboardType="email-address"
|
||||||
|
autoCapitalize="none"
|
||||||
|
style={styles.inputField}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
disabled={!firstName || (selectedStatus !== ProfileType.FAMILY_DEVICE && !lastName) || isLoading}
|
||||||
|
label={isLoading ? "Adding..." : "Add group member"}
|
||||||
|
backgroundColor="#FD1775"
|
||||||
|
style={{marginTop: 20}}
|
||||||
|
onPress={handleCreateSubUser}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Dialog>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
card: {
|
card: {
|
||||||
marginVertical: 15,
|
marginVertical: 15,
|
||||||
backgroundColor: "white",
|
backgroundColor: "white",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
borderRadius: 15,
|
borderRadius: 15,
|
||||||
padding: 20,
|
padding: 20,
|
||||||
},
|
},
|
||||||
familyCard: {
|
familyCard: {
|
||||||
marginBottom: 10,
|
marginBottom: 10,
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
width: "100%",
|
width: "100%",
|
||||||
},
|
},
|
||||||
inputField: {
|
inputField: {
|
||||||
borderRadius: 50,
|
borderRadius: 50,
|
||||||
paddingVertical: 12,
|
paddingVertical: 12,
|
||||||
paddingHorizontal: 16,
|
paddingHorizontal: 16,
|
||||||
backgroundColor: Colors.grey80,
|
backgroundColor: Colors.grey80,
|
||||||
marginBottom: 16,
|
marginBottom: 16,
|
||||||
borderColor: Colors.grey50,
|
borderColor: Colors.grey50,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
height: 40,
|
height: 40,
|
||||||
},
|
},
|
||||||
picker: {
|
picker: {
|
||||||
borderRadius: 50,
|
borderRadius: 50,
|
||||||
paddingVertical: 12,
|
paddingVertical: 12,
|
||||||
paddingHorizontal: 16,
|
paddingHorizontal: 16,
|
||||||
backgroundColor: Colors.grey80,
|
backgroundColor: Colors.grey80,
|
||||||
marginBottom: 16,
|
marginBottom: 16,
|
||||||
borderColor: Colors.grey50,
|
borderColor: Colors.grey50,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
marginTop: -20,
|
marginTop: -20,
|
||||||
height: 40,
|
height: 40,
|
||||||
},
|
},
|
||||||
label: {
|
label: {
|
||||||
marginBottom: 5,
|
marginBottom: 5,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: Colors.grey40,
|
color: Colors.grey40,
|
||||||
},
|
},
|
||||||
dialogCard: {
|
dialogCard: {
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
gap: 10,
|
gap: 10,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default MyGroup;
|
export default MyGroup;
|
||||||
|
@ -1,102 +1,102 @@
|
|||||||
import { View, Text, TextField } from "react-native-ui-lib";
|
import {Text, TextField, View} from "react-native-ui-lib";
|
||||||
import React, { useState } from "react";
|
import React, {useState} from "react";
|
||||||
import { StyleSheet } from "react-native";
|
import {StyleSheet} from "react-native";
|
||||||
import { ScrollView } from "react-native-gesture-handler";
|
import {ScrollView} from "react-native-gesture-handler";
|
||||||
import { useAuthContext } from "@/contexts/AuthContext";
|
import {useAuthContext} from "@/contexts/AuthContext";
|
||||||
import { useSettingsContext } from "@/contexts/SettingsContext";
|
import {useUpdateUserData} from "@/hooks/firebase/useUpdateUserData";
|
||||||
import { useUpdateUserData } from "@/hooks/firebase/useUpdateUserData";
|
|
||||||
const MyProfile = () => {
|
const MyProfile = () => {
|
||||||
const { user, profileData } = useAuthContext();
|
const {user, profileData} = useAuthContext();
|
||||||
|
|
||||||
const [lastName, setLastName] = useState<string>(profileData?.lastName || "");
|
const [lastName, setLastName] = useState<string>(profileData?.lastName || "");
|
||||||
const [firstName, setFirstName] = useState<string>(
|
const [firstName, setFirstName] = useState<string>(
|
||||||
profileData?.firstName || ""
|
profileData?.firstName || ""
|
||||||
);
|
);
|
||||||
|
|
||||||
const { mutateAsync: updateUserData } = useUpdateUserData();
|
const {mutateAsync: updateUserData} = useUpdateUserData();
|
||||||
return (
|
return (
|
||||||
<ScrollView style={{paddingBottom: 100, flex: 1}}>
|
<ScrollView style={{paddingBottom: 100, flex: 1}}>
|
||||||
<View style={styles.card}>
|
<View style={styles.card}>
|
||||||
<Text text70>Your Profile</Text>
|
<Text text70>Your Profile</Text>
|
||||||
<View row spread paddingH-15 centerV marginV-15>
|
<View row spread paddingH-15 centerV marginV-15>
|
||||||
<View style={styles.pfp}></View>
|
<View style={styles.pfp}></View>
|
||||||
<Text text80 color="#50be0c">
|
<Text text80 color="#50be0c">
|
||||||
Change Photo
|
Change Photo
|
||||||
</Text>
|
</Text>
|
||||||
<Text text80>Remove Photo</Text>
|
<Text text80>Remove Photo</Text>
|
||||||
</View>
|
</View>
|
||||||
<View paddingH-15>
|
<View paddingH-15>
|
||||||
<Text text80 marginT-10 marginB-7 color="#a1a1a1">
|
<Text text80 marginT-10 marginB-7 color="#a1a1a1">
|
||||||
First name
|
First name
|
||||||
</Text>
|
</Text>
|
||||||
<TextField
|
<TextField
|
||||||
text70
|
text70
|
||||||
placeholder="First name"
|
placeholder="First name"
|
||||||
style={styles.txtBox}
|
style={styles.txtBox}
|
||||||
value={firstName}
|
value={firstName}
|
||||||
onChangeText={async (value) => {
|
onChangeText={async (value) => {
|
||||||
setFirstName(value);
|
setFirstName(value);
|
||||||
await updateUserData({ newUserData: { firstName: value } });
|
await updateUserData({newUserData: {firstName: value}});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Text text80 marginT-10 marginB-7 color="#a1a1a1">
|
<Text text80 marginT-10 marginB-7 color="#a1a1a1">
|
||||||
Last name
|
Last name
|
||||||
</Text>
|
</Text>
|
||||||
<TextField
|
<TextField
|
||||||
text70
|
text70
|
||||||
placeholder="Last name"
|
placeholder="Last name"
|
||||||
style={styles.txtBox}
|
style={styles.txtBox}
|
||||||
value={lastName}
|
value={lastName}
|
||||||
onChangeText={async (value) => {
|
onChangeText={async (value) => {
|
||||||
setLastName(value);
|
setLastName(value);
|
||||||
await updateUserData({ newUserData: { lastName: value } });
|
await updateUserData({newUserData: {lastName: value}});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Text text80 marginT-10 marginB-7 color="#a1a1a1">
|
<Text text80 marginT-10 marginB-7 color="#a1a1a1">
|
||||||
Email address
|
Email address
|
||||||
</Text>
|
</Text>
|
||||||
<TextField
|
<TextField
|
||||||
text70
|
text70
|
||||||
placeholder="Email address"
|
placeholder="Email address"
|
||||||
value={user?.email?.toString()}
|
value={user?.email?.toString()}
|
||||||
style={styles.txtBox}
|
style={styles.txtBox}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={styles.card}>
|
<View style={styles.card}>
|
||||||
<Text text70>Settings</Text>
|
<Text text70>Settings</Text>
|
||||||
<Text text80 marginT-20 marginB-7 color="#a1a1a1">
|
<Text text80 marginT-20 marginB-7 color="#a1a1a1">
|
||||||
Time Zone
|
Time Zone
|
||||||
</Text>
|
</Text>
|
||||||
<TextField text70 placeholder="Time Zone" style={styles.txtBox} />
|
<TextField text70 placeholder="Time Zone" style={styles.txtBox}/>
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
card: {
|
card: {
|
||||||
marginVertical: 15,
|
marginVertical: 15,
|
||||||
backgroundColor: "white",
|
backgroundColor: "white",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
borderRadius: 15,
|
borderRadius: 15,
|
||||||
padding: 20,
|
padding: 20,
|
||||||
},
|
},
|
||||||
pfp: {
|
pfp: {
|
||||||
aspectRatio: 1,
|
aspectRatio: 1,
|
||||||
width: 60,
|
width: 60,
|
||||||
backgroundColor: "green",
|
backgroundColor: "green",
|
||||||
borderRadius: 20,
|
borderRadius: 20,
|
||||||
},
|
},
|
||||||
txtBox: {
|
txtBox: {
|
||||||
backgroundColor: "#fafafa",
|
backgroundColor: "#fafafa",
|
||||||
borderRadius: 50,
|
borderRadius: 50,
|
||||||
borderWidth: 2,
|
borderWidth: 2,
|
||||||
borderColor: "#cecece",
|
borderColor: "#cecece",
|
||||||
padding: 15,
|
padding: 15,
|
||||||
height: 45,
|
height: 45,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default MyProfile;
|
export default MyProfile;
|
||||||
|
@ -3,15 +3,22 @@ import {Button, Card, Colors, Dialog, Hint, ListItem, Text, View} from 'react-na
|
|||||||
import QRCode from 'react-native-qrcode-svg';
|
import QRCode from 'react-native-qrcode-svg';
|
||||||
import {PanningDirectionsEnum} from "react-native-ui-lib/src/components/panningViews/panningProvider";
|
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 [showHint, setShowHint] = useState(false);
|
||||||
const [showQRCodeDialog, setShowQRCodeDialog] = useState(false);
|
|
||||||
|
|
||||||
const handleShowQRCode = () => {
|
const handleShowQRCode = () => {
|
||||||
setShowHint(false);
|
setShowHint(false);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setShowQRCodeDialog(true);
|
setShowQRCodeDialog(true);
|
||||||
}, 500)
|
}, 500);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -22,9 +29,7 @@ const UserMenu = ({userId}:{userId: string}) => {
|
|||||||
color={Colors.white}
|
color={Colors.white}
|
||||||
customContent={
|
customContent={
|
||||||
<View height={18}>
|
<View height={18}>
|
||||||
<ListItem
|
<ListItem onPress={handleShowQRCode}>
|
||||||
onPress={handleShowQRCode}
|
|
||||||
>
|
|
||||||
<Text>Show Login QR Code</Text>
|
<Text>Show Login QR Code</Text>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
</View>
|
</View>
|
||||||
@ -34,7 +39,7 @@ const UserMenu = ({userId}:{userId: string}) => {
|
|||||||
backdropColor="transparent"
|
backdropColor="transparent"
|
||||||
>
|
>
|
||||||
<View>
|
<View>
|
||||||
<Button link onPress={() => setShowHint(x => !x)}>
|
<Button link onPress={() => setShowHint(!showHint)}>
|
||||||
<Text>...</Text>
|
<Text>...</Text>
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
@ -47,7 +52,7 @@ const UserMenu = ({userId}:{userId: string}) => {
|
|||||||
>
|
>
|
||||||
<Card padding-20 center>
|
<Card padding-20 center>
|
||||||
<Text marginB-10>Scan this QR Code to Login:</Text>
|
<Text marginB-10>Scan this QR Code to Login:</Text>
|
||||||
<QRCode value={userId} size={150} />
|
<QRCode value={userId} size={150}/>
|
||||||
<Button
|
<Button
|
||||||
marginT-20
|
marginT-20
|
||||||
label="Close"
|
label="Close"
|
||||||
|
@ -9,11 +9,14 @@ import * as Notifications from 'expo-notifications';
|
|||||||
import * as Device from 'expo-device';
|
import * as Device from 'expo-device';
|
||||||
import Constants from 'expo-constants';
|
import Constants from 'expo-constants';
|
||||||
import { Platform } from 'react-native';
|
import { Platform } from 'react-native';
|
||||||
|
import {useQueryClient} from "react-query";
|
||||||
|
|
||||||
|
|
||||||
export enum ProfileType {
|
export enum ProfileType {
|
||||||
"PARENT" = "parent",
|
"PARENT" = "parent",
|
||||||
"CHILD" = "child",
|
"CHILD" = "child",
|
||||||
"CAREGIVER" = "caregiver"
|
"CAREGIVER" = "caregiver",
|
||||||
|
FAMILY_DEVICE = "FAMILY_DEVICE"
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IAuthContext {
|
interface IAuthContext {
|
||||||
@ -91,6 +94,8 @@ export const AuthContextProvider: FC<{ children: ReactNode }> = ({children}) =>
|
|||||||
const {replace} = useRouter();
|
const {replace} = useRouter();
|
||||||
const ready = !initializing;
|
const ready = !initializing;
|
||||||
|
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const onAuthStateChangedHandler = async (authUser: FirebaseAuthTypes.User | null) => {
|
const onAuthStateChangedHandler = async (authUser: FirebaseAuthTypes.User | null) => {
|
||||||
setUser(authUser);
|
setUser(authUser);
|
||||||
|
|
||||||
@ -153,6 +158,17 @@ export const AuthContextProvider: FC<{ children: ReactNode }> = ({children}) =>
|
|||||||
}
|
}
|
||||||
}, [user, ready]);
|
}, [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) {
|
if (!ready) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -132,7 +132,7 @@ exports.createSubUser = onRequest(async (request, response) => {
|
|||||||
|
|
||||||
const {userType, firstName, lastName, email, password, familyId} = request.body.data;
|
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});
|
logger.warn("Missing required fields in request body", {requestBody: request.body.data});
|
||||||
response.status(400).json({error: "Missing required fields"});
|
response.status(400).json({error: "Missing required fields"});
|
||||||
return;
|
return;
|
||||||
|
@ -2,6 +2,7 @@ import {useMutation, useQueryClient} from "react-query";
|
|||||||
import {UserProfile} from "@/hooks/firebase/types/profileTypes";
|
import {UserProfile} from "@/hooks/firebase/types/profileTypes";
|
||||||
import functions from '@react-native-firebase/functions';
|
import functions from '@react-native-firebase/functions';
|
||||||
import {ProfileType, useAuthContext} from "@/contexts/AuthContext";
|
import {ProfileType, useAuthContext} from "@/contexts/AuthContext";
|
||||||
|
import {HttpsCallableResult} from "@firebase/functions";
|
||||||
|
|
||||||
export const useCreateSubUser = () => {
|
export const useCreateSubUser = () => {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
@ -9,13 +10,13 @@ export const useCreateSubUser = () => {
|
|||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationKey: ["createSubUser"],
|
mutationKey: ["createSubUser"],
|
||||||
mutationFn: async ({email, ...userProfile}: { email: string } & UserProfile) => {
|
mutationFn: async ({email, ...userProfile}: { email?: string } & UserProfile) => {
|
||||||
if (profileType === ProfileType.PARENT) {
|
if (profileType === ProfileType.PARENT) {
|
||||||
return await functions().httpsCallable("createSubUser")({
|
return await functions().httpsCallable("createSubUser")({
|
||||||
...userProfile,
|
...userProfile,
|
||||||
email,
|
email,
|
||||||
familyId: profileData?.familyId
|
familyId: profileData?.familyId
|
||||||
})
|
}) as HttpsCallableResult<{ userId: string }>
|
||||||
} else {
|
} else {
|
||||||
throw Error("Can't create sub-users as a non-parent.")
|
throw Error("Can't create sub-users as a non-parent.")
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user