mirror of
https://github.com/urosran/cally.git
synced 2025-08-25 13:49:39 +00:00
LOgging in with qr code added
This commit is contained in:
@ -1,22 +1,60 @@
|
||||
import {Button, ButtonSize, Text, TextField, View} from "react-native-ui-lib";
|
||||
import React, {useState} from "react";
|
||||
import {Button, ButtonSize, Dialog, Text, TextField, View} from "react-native-ui-lib";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {useSignIn} from "@/hooks/firebase/useSignIn";
|
||||
import {StyleSheet} from "react-native";
|
||||
import Toast from 'react-native-toast-message';
|
||||
import {useLoginWithQrCode} from "@/hooks/firebase/useLoginWithQrCode";
|
||||
import {Camera, CameraView} from 'expo-camera';
|
||||
import {BarCodeScanner} from "expo-barcode-scanner";
|
||||
|
||||
const SignInPage = ({setTab}: { setTab: React.Dispatch<React.SetStateAction<"register" | "login" | "reset-password">> }) => {
|
||||
const [email, setEmail] = useState<string>("");
|
||||
const [password, setPassword] = useState<string>("");
|
||||
const [hasPermission, setHasPermission] = useState<boolean | null>(null);
|
||||
const [scanned, setScanned] = useState<boolean>(false);
|
||||
const [showCameraDialog, setShowCameraDialog] = useState<boolean>(false);
|
||||
|
||||
const {mutateAsync: signIn, error, isError} = useSignIn();
|
||||
const {mutateAsync: signInWithQrCode} = useLoginWithQrCode()
|
||||
|
||||
const handleSignIn = async () => {
|
||||
await signIn({email, password});
|
||||
if(!isError) {
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Password successfully reset, please check your messages."
|
||||
})
|
||||
text1: "Login successful!"
|
||||
});
|
||||
} else {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Error logging in",
|
||||
text2: `${error}`
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleQrCodeScanned = async ({ data }: { data: string }) => {
|
||||
setShowCameraDialog(false);
|
||||
try {
|
||||
await signInWithQrCode({ userId: data });
|
||||
Toast.show({
|
||||
type: "success",
|
||||
text1: "Login successful with QR code!"
|
||||
});
|
||||
} catch (err) {
|
||||
Toast.show({
|
||||
type: "error",
|
||||
text1: "Error logging in with QR code",
|
||||
text2: `${err}`
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getCameraPermissions = async (callback: () => void) => {
|
||||
const { status } = await Camera.requestCameraPermissionsAsync();
|
||||
setHasPermission(status === 'granted');
|
||||
if(status === 'granted') {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
@ -41,6 +79,15 @@ const SignInPage = ({setTab}: { setTab: React.Dispatch<React.SetStateAction<"re
|
||||
style={{marginBottom: 20}}
|
||||
backgroundColor="#fd1775"
|
||||
/>
|
||||
<Button
|
||||
label="Login with a QR Code"
|
||||
onPress={() => {
|
||||
getCameraPermissions(() => setShowCameraDialog(true));
|
||||
}
|
||||
}
|
||||
style={{marginBottom: 20}}
|
||||
backgroundColor="#fd1775"
|
||||
/>
|
||||
{isError && <Text center style={{marginBottom: 20}}>{`${error}`}</Text>}
|
||||
|
||||
<View row centerH marginB-5 gap-5>
|
||||
@ -76,6 +123,36 @@ const SignInPage = ({setTab}: { setTab: React.Dispatch<React.SetStateAction<"re
|
||||
color="#fd1775"
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Camera Dialog */}
|
||||
<Dialog
|
||||
visible={showCameraDialog}
|
||||
onDismiss={() => setShowCameraDialog(false)}
|
||||
bottom
|
||||
width="100%"
|
||||
height="70%"
|
||||
containerStyle={{ padding: 0 }}
|
||||
>
|
||||
{hasPermission === null ? (
|
||||
<Text>Requesting camera permissions...</Text>
|
||||
) : !hasPermission ? (
|
||||
<Text>No access to camera</Text>
|
||||
) : (
|
||||
<CameraView
|
||||
style={{ flex: 1 }}
|
||||
onBarcodeScanned={handleQrCodeScanned}
|
||||
barcodeScannerSettings={{
|
||||
barcodeTypes: ["qr"],
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
label="Cancel"
|
||||
onPress={() => setShowCameraDialog(false)}
|
||||
backgroundColor="#fd1775"
|
||||
style={{ margin: 10 }}
|
||||
/>
|
||||
</Dialog>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
@ -1,20 +1,34 @@
|
||||
import {Avatar, Card, Colors, FloatingButton, Text, View} from "react-native-ui-lib";
|
||||
import {
|
||||
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 UserMenu from "@/components/pages/settings/user_settings_views/UserMenu";
|
||||
|
||||
const MyGroup = () => {
|
||||
const [showAddUserDialog, setShowAddUserDialog] = useState(false);
|
||||
const [showNewUserInfoDialog, setShowNewUserInfoDialog] = useState(false);
|
||||
const [selectedStatus, setSelectedStatus] = useState<string | PickerSingleValue>('CHILD');
|
||||
const [selectedStatus, setSelectedStatus] = useState<string | PickerSingleValue>(ProfileType.CHILD);
|
||||
const [firstName, setFirstName] = useState('');
|
||||
const [lastName, setLastName] = useState('');
|
||||
const [email, setEmail] = useState('');
|
||||
|
||||
const {mutateAsync: createSubUser, isLoading: loading, isError} = useCreateSubUser();
|
||||
const {mutateAsync: createSubUser, isLoading, isError} = useCreateSubUser();
|
||||
const {data: familyMembers} = useGetFamilyMembers(true);
|
||||
|
||||
const parents = familyMembers?.filter(x => x.userType === ProfileType.PARENT) ?? [];
|
||||
@ -45,13 +59,15 @@ const MyGroup = () => {
|
||||
}
|
||||
};
|
||||
|
||||
console.log(familyMembers)
|
||||
|
||||
return (
|
||||
<View style={{flex: 1, height: "100%"}}>
|
||||
<View>
|
||||
<ScrollView style={styles.card}>
|
||||
{(!parents.length && !children.length && !caregivers.length) && (
|
||||
<Text text70 marginV-10>
|
||||
No user devices added
|
||||
{isLoading ? "Loading...." : "No user devices added"}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
@ -73,6 +89,10 @@ const MyGroup = () => {
|
||||
<Text text90
|
||||
grey40>{member.userType === ProfileType.PARENT ? "Admin (You)" : "Child"}</Text>
|
||||
</View>
|
||||
|
||||
<View flex-1/>
|
||||
|
||||
<UserMenu userId={member?.uid!}/>
|
||||
</Card>
|
||||
))}
|
||||
</>
|
||||
@ -105,10 +125,108 @@ const MyGroup = () => {
|
||||
<FloatingButton fullWidth hideBackgroundOverlay visible
|
||||
button={{label: '+ Add a user device', onPress: () => setShowAddUserDialog(true)}}/>
|
||||
|
||||
{/* Add user dialog here */}
|
||||
<Dialog
|
||||
visible={showAddUserDialog}
|
||||
onDismiss={() => setShowAddUserDialog(false)}
|
||||
panDirection={PanningProvider.Directions.DOWN}
|
||||
>
|
||||
<Card padding-25 gap-10>
|
||||
<Text>
|
||||
Add a new user device
|
||||
</Text>
|
||||
|
||||
{/* New user information dialog here */}
|
||||
<Button backgroundColor={"#FD1775"}><Text white>Show a QR Code</Text></Button>
|
||||
<Button backgroundColor={"#05A8B6"} onPress={() => {
|
||||
setShowAddUserDialog(false)
|
||||
setTimeout(() => {
|
||||
setShowNewUserInfoDialog(true)
|
||||
}, 500)
|
||||
}}><Text white>Enter email address</Text></Button>
|
||||
|
||||
|
||||
<TouchableOpacity onPress={() => setShowAddUserDialog(false)} center><Text>Return to user
|
||||
settings</Text></TouchableOpacity>
|
||||
</Card>
|
||||
</Dialog>
|
||||
|
||||
<Dialog
|
||||
panDirection={PanningProvider.Directions.DOWN}
|
||||
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>
|
||||
<Avatar imageStyle={{borderRadius: 10}} containerStyle={{borderRadius: 10,}} size={60}
|
||||
backgroundColor={Colors.grey60}/>
|
||||
<TouchableOpacity onPress={() => {
|
||||
}}>
|
||||
<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>
|
||||
|
||||
|
||||
<Text style={styles.label}>First Name</Text>
|
||||
<TextField
|
||||
editable={!isLoading}
|
||||
placeholder="First name"
|
||||
value={firstName}
|
||||
onChangeText={setFirstName}
|
||||
style={styles.inputField}
|
||||
/>
|
||||
|
||||
<Text style={styles.label}>Last Name</Text>
|
||||
<TextField
|
||||
editable={!isLoading}
|
||||
placeholder="Last name"
|
||||
value={lastName}
|
||||
onChangeText={setLastName}
|
||||
style={styles.inputField}
|
||||
/>
|
||||
|
||||
<Text style={styles.label}>Email Address</Text>
|
||||
<TextField
|
||||
editable={!isLoading}
|
||||
placeholder="Email address"
|
||||
value={email}
|
||||
onChangeText={setEmail}
|
||||
keyboardType="email-address"
|
||||
autoCapitalize="none"
|
||||
style={styles.inputField}
|
||||
/>
|
||||
|
||||
<Button
|
||||
disabled={!firstName || !lastName || !email || isLoading}
|
||||
label={isLoading ? "Adding..." : "Add group member"}
|
||||
backgroundColor="#FD1775"
|
||||
style={{marginTop: 20}}
|
||||
onPress={handleCreateSubUser}
|
||||
/>
|
||||
</Card>
|
||||
</Dialog>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
62
components/pages/settings/user_settings_views/UserMenu.tsx
Normal file
62
components/pages/settings/user_settings_views/UserMenu.tsx
Normal file
@ -0,0 +1,62 @@
|
||||
import React, {useState} from 'react';
|
||||
import {Button, Card, Colors, Dialog, Hint, ListItem, Text, View} from 'react-native-ui-lib';
|
||||
import QRCode from 'react-native-qrcode-svg';
|
||||
import {PanningDirectionsEnum} from "react-native-ui-lib/src/components/panningViews/panningProvider";
|
||||
|
||||
const UserMenu = ({userId}:{userId: string}) => {
|
||||
const [showHint, setShowHint] = useState(false);
|
||||
const [showQRCodeDialog, setShowQRCodeDialog] = useState(false);
|
||||
|
||||
const handleShowQRCode = () => {
|
||||
setShowHint(false);
|
||||
setTimeout(() => {
|
||||
setShowQRCodeDialog(true);
|
||||
}, 500)
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Hint
|
||||
onBackgroundPress={() => setShowHint(false)}
|
||||
onPress={() => setShowHint(true)}
|
||||
color={Colors.white}
|
||||
customContent={
|
||||
<View height={18}>
|
||||
<ListItem
|
||||
onPress={handleShowQRCode}
|
||||
>
|
||||
<Text>Show Login QR Code</Text>
|
||||
</ListItem>
|
||||
</View>
|
||||
}
|
||||
enableShadow
|
||||
visible={showHint}
|
||||
backdropColor="transparent"
|
||||
>
|
||||
<View>
|
||||
<Button link onPress={() => setShowHint(x => !x)}>
|
||||
<Text>...</Text>
|
||||
</Button>
|
||||
</View>
|
||||
</Hint>
|
||||
|
||||
<Dialog
|
||||
visible={showQRCodeDialog}
|
||||
onDismiss={() => setShowQRCodeDialog(false)}
|
||||
panDirection={PanningDirectionsEnum.DOWN}
|
||||
>
|
||||
<Card padding-20 center>
|
||||
<Text marginB-10>Scan this QR Code to Login:</Text>
|
||||
<QRCode value={userId} size={150} />
|
||||
<Button
|
||||
marginT-20
|
||||
label="Close"
|
||||
onPress={() => setShowQRCodeDialog(false)}
|
||||
/>
|
||||
</Card>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserMenu;
|
Reference in New Issue
Block a user