Feature/notification system #26

Open
mattdimegs wants to merge 34 commits from feature/notification-system into main
4 changed files with 219 additions and 164 deletions
Showing only changes of commit 0a857f86a7 - Show all commits

View file

@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { View, Text } from 'react-native'; import { View, Text, ScrollView, KeyboardAvoidingView, Platform } from 'react-native';
import { StatusBar } from 'expo-status-bar'; import { StatusBar } from 'expo-status-bar';
import { useFormik } from 'formik'; import { useFormik } from 'formik';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
@ -65,68 +65,78 @@ export default function Login() {
}, [formValues]); }, [formValues]);
return ( return (
<React.Fragment> <View style={{ flex: 1 }}>
<StatusBar style="dark" />
<PageHeader> <PageHeader>
<View style={{ flexDirection: 'row', height: 80, alignItems: 'center' }} /> <View style={{ flexDirection: 'row', height: 80, alignItems: 'center' }} />
</PageHeader> </PageHeader>
<StyledContainer> <KeyboardAvoidingView
<StatusBar style="dark" /> style={{ flex: 1 }}
<SafeAreaView /> behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
<InnerContainer> keyboardVerticalOffset={0}
<PageImage resizeMode="cover" source={require('./../assets/images/tones-logo.png')} /> >
<Title>Tones</Title> <ScrollView keyboardShouldPersistTaps="handled" contentContainerStyle={{ flexGrow: 1 }}>
<SubTitle>Account Login</SubTitle> <StyledContainer>
<StyledFormArea> <SafeAreaView />
{error ? <MessageBox style={{ color: 'red' }}>{error}</MessageBox> : null} <InnerContainer>
<LoginTextInput <PageImage resizeMode="cover" source={require('./../assets/images/tones-logo.png')} />
label="Email Address" <Title>Tones</Title>
icon="mail-outline" <SubTitle>Account Login</SubTitle>
placeholder="test@organization.com" <StyledFormArea>
placeholderTextColor="gray" {error ? <MessageBox style={{ color: 'red' }}>{error}</MessageBox> : null}
onChangeText={formik.handleChange('email')} <LoginTextInput
onBlur={formik.handleBlur('email')} label="Email Address"
value={formik.values.email} icon="mail-outline"
keyboardType="email-address" placeholder="test@organization.com"
/> placeholderTextColor="gray"
<LoginTextInput onChangeText={formik.handleChange('email')}
label="Password" onBlur={formik.handleBlur('email')}
icon="lock-closed-outline" value={formik.values.email}
placeholder="* * * * * *" keyboardType="email-address"
placeholderTextColor="gray" autoComplete='email'
onChangeText={formik.handleChange('password')} autoCapitalize='none'
onBlur={formik.handleBlur('password')} />
value={formik.values.password} <LoginTextInput
secureTextEntry={hidePassword} label="Password"
isPassword icon="lock-closed-outline"
hidePassword={hidePassword} placeholder="* * * * * *"
setHidePassword={setHidePassword} placeholderTextColor="gray"
/> onChangeText={formik.handleChange('password')}
<MessageBox>...</MessageBox> onBlur={formik.handleBlur('password')}
<StyledButton onPress={formik.handleSubmit} disabled={loginButtonDisabled} style={loginButtonDisabled ? {backgroundColor: 'grey'} : {}}> value={formik.values.password}
<ButtonText>Login</ButtonText> secureTextEntry={hidePassword}
</StyledButton> isPassword
hidePassword={hidePassword}
setHidePassword={setHidePassword}
/>
<MessageBox>...</MessageBox>
<StyledButton onPress={formik.handleSubmit} disabled={loginButtonDisabled} style={loginButtonDisabled ? { backgroundColor: 'grey' } : {}}>
<ButtonText>Login</ButtonText>
</StyledButton>
<Line />
<ExtraView>
<ExtraText>Don't have an account already? </ExtraText>
<Link href='./register'>
<TextLinkContent>Register</TextLinkContent>
</Link>
</ExtraView>
</StyledFormArea>
<Line /> <Line />
<ExtraView> <Text>Temporary Area:</Text>
<ExtraText>Don't have an account already? </ExtraText> <View>
<Link href='./register'> <Link href='./incidents'>
<TextLinkContent>Register</TextLinkContent> <TextLinkContent>Incidents</TextLinkContent>
</Link> </Link>
</ExtraView> <Link href='./landing'>
</StyledFormArea> <TextLinkContent>Landing</TextLinkContent>
<Line /> </Link>
<Text>Temporary Area:</Text> </View>
<View> <Text>View Token: {expoPushToken}</Text>
<Link href='./incidents'> <Line />
<TextLinkContent>Incidents</TextLinkContent> </InnerContainer>
</Link> </StyledContainer>
<Link href='./landing'> </ScrollView>
<TextLinkContent>Landing</TextLinkContent> </KeyboardAvoidingView>
</Link> </View>
</View> );
<Text>View Token: {expoPushToken}</Text> }
<Line />
</InnerContainer>
</StyledContainer>
</React.Fragment>
);
}

View file

@ -1,9 +1,10 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { router } from 'expo-router'; import { router } from 'expo-router';
import { createUserWithEmailAndPassword, updateProfile } from 'firebase/auth'; import { createUserWithEmailAndPassword, updateProfile } from 'firebase/auth';
import { getDatabase, ref, set } from 'firebase/database';
import { auth } from '@/contexts/firebase'; import { auth } from '@/contexts/firebase';
import { useAuth } from '@/contexts/AuthContext'; import { useAuth } from '@/contexts/AuthContext';
import { View, ScrollView, Text, TouchableOpacity } from 'react-native'; import { View, ScrollView, Text, TouchableOpacity, KeyboardAvoidingView, Platform } from 'react-native';
import { StatusBar } from 'expo-status-bar'; import { StatusBar } from 'expo-status-bar';
import { useFormik } from 'formik'; import { useFormik } from 'formik';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
@ -22,7 +23,8 @@ import {
MessageBox, MessageBox,
LoginTextInput, LoginTextInput,
RegisterDropdownInput, RegisterDropdownInput,
} from '../components/generalHelpers.jsx'; formatPhoneNumber
} from '@/components/generalHelpers.jsx';
export default function Register() { export default function Register() {
const [providerDropdownOpen, setProviderDropdownOpen] = useState(false); const [providerDropdownOpen, setProviderDropdownOpen] = useState(false);
@ -50,6 +52,16 @@ export default function Register() {
await updateProfile(userCredential.user, { await updateProfile(userCredential.user, {
displayName: `${values.firstName} ${values.lastName}` displayName: `${values.firstName} ${values.lastName}`
}); });
const db = getDatabase();
await set(ref(db, 'users/' + userCredential.user.uid), {
firstName: values.firstName,
lastName: values.lastName,
number: values.number,
provider: values.provider,
email: values.email,
uid: userCredential.user.uid
});
router.replace('./incidents'); router.replace('./incidents');
} catch (err) { } catch (err) {
setError(err.message); setError(err.message);
@ -63,21 +75,20 @@ export default function Register() {
useEffect(() => { useEffect(() => {
if (formValues) { if (formValues) {
if ( const regex = /^[^@]+@(?:[a-zA-Z0-9-]+\.)+[a-zA-Z0-9-]+$/;
formValues.email && if ((formValues.number.length === 14 || (formValues.number.length === 10 && !formValues.number.includes('('))) && regex.test(formValues.email) && formValues.firstName && formValues.lastName) {
formValues.firstName && if (formValues.password.length !== 0 && (formValues.password === formValues.passwordConfirmation)) {
formValues.lastName && setRegisterButtonDisabled(false);
formValues.password.length !== 0 && } else {
formValues.password === formValues.passwordConfirmation setRegisterButtonDisabled(true);
) { }
setRegisterButtonDisabled(false);
} else { } else {
setRegisterButtonDisabled(true); setRegisterButtonDisabled(true);
} }
} else { } else {
setRegisterButtonDisabled(true); setRegisterButtonDisabled(true);
} }
}, [formValues]); }, [formValues])
useEffect(() => { useEffect(() => {
if (user) { if (user) {
@ -86,7 +97,8 @@ export default function Register() {
}, [user]); }, [user]);
return ( return (
<View> <View style={{ flex: 1 }}>
<StatusBar style="dark" />
<PageHeader> <PageHeader>
<View style={{ flexDirection: 'row', height: 80, alignItems: 'flex-end' }}> <View style={{ flexDirection: 'row', height: 80, alignItems: 'flex-end' }}>
<TouchableOpacity onPress={router.back} style={{ flexDirection: 'row', alignItems: 'center', paddingBottom: 5 }}> <TouchableOpacity onPress={router.back} style={{ flexDirection: 'row', alignItems: 'center', paddingBottom: 5 }}>
@ -95,97 +107,105 @@ export default function Register() {
</TouchableOpacity> </TouchableOpacity>
</View> </View>
</PageHeader> </PageHeader>
<ScrollView> <KeyboardAvoidingView
<StyledContainer> style={{ flex: 1 }}
<StatusBar style="dark" /> behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
keyboardVerticalOffset={0}
>
<ScrollView keyboardShouldPersistTaps="handled" contentContainerStyle={{ flexGrow: 1 }}>
<SafeAreaView /> <SafeAreaView />
<InnerContainer> <StyledContainer>
<PageImage resizeMode="cover" source={require('./../assets/images/tones-logo.png')} /> <InnerContainer>
{error ? <MessageBox style={{ color: 'red' }}>{error}</MessageBox> : null} <PageImage resizeMode="cover" source={require('./../assets/images/tones-logo.png')} />
<Title>Tones</Title> {error ? <MessageBox style={{ color: 'red' }}>{error}</MessageBox> : null}
<SubTitle>Account Register</SubTitle> <Title>Tones</Title>
<StyledFormArea> <SubTitle>Account Register</SubTitle>
<LoginTextInput <StyledFormArea>
label="First Name" <LoginTextInput
icon="person-outline" label="First Name"
placeholder="Bud" icon="person-outline"
placeholderTextColor="gray" placeholder="Bud"
onChangeText={formik.handleChange('firstName')} placeholderTextColor="gray"
onBlur={formik.handleBlur('firstName')} onChangeText={formik.handleChange('firstName')}
value={formik.values.firstName} onBlur={formik.handleBlur('firstName')}
/> value={formik.values.firstName}
<LoginTextInput />
label="Last Name" <LoginTextInput
icon="people-outline" label="Last Name"
placeholder="Doble" icon="people-outline"
placeholderTextColor="gray" placeholder="Doble"
onChangeText={formik.handleChange('lastName')} placeholderTextColor="gray"
onBlur={formik.handleBlur('lastName')} onChangeText={formik.handleChange('lastName')}
value={formik.values.lastName} onBlur={formik.handleBlur('lastName')}
/> value={formik.values.lastName}
<LoginTextInput />
label="Phone Number" <LoginTextInput
icon="call-outline" label="Phone Number"
placeholder="123-456-7890" icon="call-outline"
placeholderTextColor="gray" placeholder="123-456-7890"
onChangeText={formik.handleChange('number')} placeholderTextColor="gray"
onBlur={formik.handleBlur('number')} onChangeText={formik.handleChange('number')}
value={formik.values.number.replace(/^(\d{3})(\d{3})(\d+)$/, "($1) $2-$3")} onBlur={formik.handleBlur('number')}
keyboardType="number-pad" value={formatPhoneNumber(formik.values.number)}
maxLength={14} autoComplete='tel'
/> keyboardType='phone-pad'
<RegisterDropdownInput maxLength={14}
label="Cellular Provider" />
isOpen={providerDropdownOpen} <RegisterDropdownInput
setOpen={setProviderDropdownOpen} label="Cellular Provider"
selectedValue={formik.values.provider} isOpen={providerDropdownOpen}
onValueChange={formik.handleChange('provider')} setOpen={setProviderDropdownOpen}
menu={providerMenu} selectedValue={formik.values.provider}
/> onValueChange={formik.handleChange('provider')}
<LoginTextInput menu={providerMenu}
label="Email Address" />
icon="mail-outline" <LoginTextInput
placeholder="test@organization.com" label="Email Address"
placeholderTextColor="gray" icon="mail-outline"
onChangeText={formik.handleChange('email')} placeholder="test@organization.com"
onBlur={formik.handleBlur('email')} placeholderTextColor="gray"
value={formik.values.email} onChangeText={formik.handleChange('email')}
keyboardType="email-address" onBlur={formik.handleBlur('email')}
/> value={formik.values.email}
<LoginTextInput keyboardType="email-address"
label="Password" autoComplete='email'
icon="lock-closed-outline" autoCapitalize='none'
placeholder="* * * * * *" />
placeholderTextColor="gray" <LoginTextInput
onChangeText={formik.handleChange('password')} label="Password"
onBlur={formik.handleBlur('password')} icon="lock-closed-outline"
value={formik.values.password} placeholder="* * * * * *"
secureTextEntry={hidePassword} placeholderTextColor="gray"
isPassword onChangeText={formik.handleChange('password')}
hidePassword={hidePassword} onBlur={formik.handleBlur('password')}
setHidePassword={setHidePassword} value={formik.values.password}
/> secureTextEntry={hidePassword}
<LoginTextInput isPassword
label="Confirm Password" hidePassword={hidePassword}
icon="shield-checkmark-outline" setHidePassword={setHidePassword}
placeholder="* * * * * *" />
placeholderTextColor="gray" <LoginTextInput
onChangeText={formik.handleChange('passwordConfirmation')} label="Confirm Password"
onBlur={formik.handleBlur('passwordConfirmation')} icon="shield-checkmark-outline"
value={formik.values.passwordConfirmation} placeholder="* * * * * *"
secureTextEntry={hidePassword} placeholderTextColor="gray"
isPassword onChangeText={formik.handleChange('passwordConfirmation')}
hidePassword={hidePassword} onBlur={formik.handleBlur('passwordConfirmation')}
setHidePassword={setHidePassword} value={formik.values.passwordConfirmation}
/> secureTextEntry={hidePassword}
<MessageBox>...</MessageBox> isPassword
<StyledButton onPress={formik.handleSubmit} style={registerButtonDisabled ? {backgroundColor: 'grey'} : {}}> hidePassword={hidePassword}
<ButtonText>Register</ButtonText> setHidePassword={setHidePassword}
</StyledButton> />
</StyledFormArea> <MessageBox>...</MessageBox>
</InnerContainer> <StyledButton onPress={formik.handleSubmit} style={registerButtonDisabled ? { backgroundColor: 'grey' } : {}}>
</StyledContainer> <ButtonText>Register</ButtonText>
</ScrollView> </StyledButton>
</StyledFormArea>
</InnerContainer>
</StyledContainer>
</ScrollView>
</KeyboardAvoidingView>
</View> </View>
) )
} }

View file

@ -336,4 +336,28 @@ export const RegisterDropdownInput = ({
</TouchableOpacity> </TouchableOpacity>
</Container> </Container>
) )
} }
export const formatPhoneNumber = (e) => {
let formattedNumber;
const length = e?.length;
const regex = () => e.replace(/[^0-9\.]+/g, "");
const areaCode = () => `(${regex().slice(0, 3)})`;
const firstSix = () => `${areaCode()} ${regex().slice(3, 6)}`;
const trailer = (start) => `${regex().slice(start, regex().length)}`;
if (length <= 3) {
formattedNumber = regex();
} else if (length === 4) {
formattedNumber = `${areaCode()} ${trailer(3)}`;
} else if (length === 5) {
formattedNumber = `${areaCode().replace(")", "")}`;
} else if (length >= 5 && length <= 9) {
formattedNumber = `${areaCode()} ${trailer(3)}`;
} else if (length >= 10) {
formattedNumber = `${firstSix()}-${trailer(6)}`;
}
return formattedNumber;
};

View file

@ -5,6 +5,7 @@ import ReactNativeAsyncStorage from '@react-native-async-storage/async-storage';
const firebaseConfig = { const firebaseConfig = {
apiKey: process.env.EXPO_PUBLIC_FIREBASE_API_KEY, apiKey: process.env.EXPO_PUBLIC_FIREBASE_API_KEY,
authDomain: process.env.EXPO_PUBLIC_FIREBASE_AUTH_DOMAIN, authDomain: process.env.EXPO_PUBLIC_FIREBASE_AUTH_DOMAIN,
databaseURL: process.env.EXPO_PUBLIC_FIREBASE_DATABASE_URL,
projectId: process.env.EXPO_PUBLIC_FIREBASE_PROJECT_ID, projectId: process.env.EXPO_PUBLIC_FIREBASE_PROJECT_ID,
storageBucket: process.env.EXPO_PUBLIC_FIREBASE_STORAGE_BUCKET, storageBucket: process.env.EXPO_PUBLIC_FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.EXPO_PUBLIC_FIREBASE_MESSAGING_SENDER_ID, messagingSenderId: process.env.EXPO_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,