Skip to content

Instantly share code, notes, and snippets.

@bosz
Created April 14, 2023 14:05
Show Gist options
  • Save bosz/ce0b95e91ce8f591ebb35058e0b2c5a1 to your computer and use it in GitHub Desktop.
Save bosz/ce0b95e91ce8f591ebb35058e0b2c5a1 to your computer and use it in GitHub Desktop.
import React, {useState, useEffect} from 'react';
import {
View,
Alert,
ScrollView,
TextInput,
TouchableOpacity,
ActivityIndicator,
} from 'react-native';
import auth from '@react-native-firebase/auth';
import {MyPage, Text, Navbar, SelectInput} from '../../components';
import Toast from 'react-native-toast-message';
import {COUNTRY_PHONE_EXTENSION_CODES} from './../../res/data';
import styles from './PhoneAuth.style';
import theme from '../../style/theme';
import {API_URL} from '@env';
import {generateValidationErrorString, STORAGE} from '../../utility';
import {useDispatch, useSelector} from 'react-redux';
import {onSignupSuccesfull, setAuthModalStatus} from '../../redux/profileSlice';
const Input = props => {
return (
<TextInput
{...props}
placeholder={props.placeholder ?? 'Insert text here'}
style={[styles.textInput, props.style]}
onChangeText={props.onChangeText}
keyboardType={props.keyboardType}
secureTextEntry={props.secureTextEntry}
autoCapitalize={props.autoCapitalize ?? 'sentences'}
defaultValue={props.value}
placeholderTextColor="#35465d44"
/>
);
};
const PhoneAuth = props => {
const dispatch = useDispatch();
const [name, setName] = useState('');
const [firebaseUser, setFirebaseUser] = useState('');
const [email, setEmail] = useState('');
const [phoneNumber, setPhoneNumber] = useState('');
const [invalidCodeError, setInvalidCodeError] = useState('');
const [fullPhoneNumber, setFullPhoneNumber] = useState('');
const [countryCode, setCountryCode] = useState('+');
const [password, setPassword] = useState('');
const [formError, setFormError] = useState('');
const [passwordConfirmation, setPasswordConfirmation] = useState('');
const [step, setStep] = useState(1);
const [loading, setLoading] = useState(false);
const [confirm, setConfirm] = useState(null);
const [code, setCode] = useState('');
const device = useSelector(state => state.profile.device)
useEffect(() => {
const subscriber = auth().onAuthStateChanged(onAuthStateChanged);
return subscriber; // unsubscribe on unmount
}, []);
useEffect(() => {
if (!countryCode.length) {
setCountryCode('+');
} else {
setFullPhoneNumber(countryCode + phoneNumber);
}
}, [countryCode, phoneNumber]);
useEffect(() => {
if (code.length == 6) {
confirmCode();
}
}, [code]);
useEffect(() => {
if (firebaseUser && confirm) {
attemptLogin();
}
}, [firebaseUser]);
// Handle user state changes
function onAuthStateChanged(user) {
setFirebaseUser(user);
}
// STEP 1: Handle the button press
async function signInWithPhoneNumber() {
const isValidCode = COUNTRY_PHONE_EXTENSION_CODES.some(
standardCountryCode => standardCountryCode.dial_code == countryCode,
);
if (!isValidCode) {
return Alert.alert(
'Invalid country code',
'Input a valid country phone code to proceed',
);
}
if (!phoneNumber.length) {
return Alert.alert(
'Invalid phone number',
'Input a valid phone number to proceed',
);
}
setLoading(true);
const confirmation = await auth().signInWithPhoneNumber(fullPhoneNumber);
setConfirm(confirmation);
setStep(2);
setLoading(false);
}
// STEP 2: Confirm code
async function confirmCode() {
setLoading(true);
try {
await confirm.confirm(code);
attemptLogin();
} catch (error) {
setInvalidCodeError('The code you entered is invalid. Try again');
}
setLoading(false);
}
// STEP 2.5 - Attempt to login with the phone number and uid (provider_id)
const attemptLogin = () => {
setLoading(true);
let url = `${API_URL}/auth/phone/login`;
let formData = new FormData();
formData.append('phone_number', fullPhoneNumber);
formData.append('provider_id', firebaseUser.uid);
device?.userId && formBody.append('device_id', device.userId);
fetch(url, {method: 'POST', body: formData})
.then(response => {
const statusCode = response.status;
const responseJson = response.json();
return Promise.all([statusCode, responseJson]);
})
.then(res => {
const statusCode = res[0];
const responseJson = res[1];
if (statusCode == 200) {
// Success - Account found
dispatch(setAuthModalStatus(false));
dispatch(onSignupSuccesfull(responseJson));
STORAGE.saveObject('PROFILE', responseJson);
Toast.show({
type: 'success',
text1: 'Login successful',
text2: `Welcome ${responseJson.user.name} back to nuova. Enjoy the launch 🚀🚀🚀 !!!`,
});
} else if (statusCode == 404) {
// Account not found
setStep(3);
} else if (statusCode == 422) {
Alert.alert('Invalid params', 'Some fields are required');
} else {
Alert.alert('Error', 'Something unexepcted happened');
}
})
.catch(err => Alert.alert('Server error', err.message))
.finally(fin => setLoading(false));
};
const completeRegistration = () => {
setLoading(true);
// const {device} = this.props;
// VALIDATION
let validationErrors = [];
if (name.length == 0) {
validationErrors.push({name: 'Name not provided'});
}
if (email.length > 0) {
// if (!_validateEmail(email)) {
// validationErrors.push({email: 'Invalid email'});
// }
}
if (password.length < 6) {
validationErrors.push({
password: 'Password must be more than 6 characters',
});
}
if (password != passwordConfirmation) {
validationErrors.push({
password_confirmation: 'Password donnot match',
});
}
if (validationErrors.length) {
let errorString = generateValidationErrorString(validationErrors);
Alert.alert('Error', errorString);
setLoading(false);
return false;
}
let url = `${API_URL}/auth/phone/complete-registration`;
let formData = new FormData();
formData.append('phone_number', fullPhoneNumber);
formData.append('code', code);
formData.append('name', name);
formData.append('email', email);
formData.append('password', password);
formData.append('password_confirmation', passwordConfirmation);
formData.append('provider_id', firebaseUser.uid);
device?.userId && formBody.append('device_id', device.userId);
fetch(url, {method: 'POST', body: formData})
.then(response => {
const statusCode = response.status;
const responseJson = response.json();
return Promise.all([statusCode, responseJson]);
})
.then(res => {
const statusCode = res[0];
const responseJson = res[1];
if (statusCode == 200) {
dispatch(setAuthModalStatus(false));
dispatch(onSignupSuccesfull(responseJson));
STORAGE.saveObject('PROFILE', responseJson);
Toast.show({
type: 'success',
text1: 'Login successful',
text2: `Welcome ${responseJson.user.name} back to nuova. Enjoy the launch 🚀🚀🚀 !!!`,
});
} else if (statusCode == 422) {
return Alert.alert('Invalid fields', responseJson.message);
} else if (statusCode == 400) {
Alert.alert('Error', responseJson.message);
} else {
Alert.alert('Error', 'Something unexpected happened');
}
})
.catch(err => {
Alert.alert('Server Error', err.message);
})
.finally(fin => setLoading(false));
};
return (
<MyPage
navBar={
<Navbar
onBackPressed={() => props.onBackPressed()}
title={'Phone number sign in'}
{...props}
/>
}
contentContainerStyle={{paddingTop: 0, paddingHorizontal: 0}}>
<ScrollView
keyboardShouldPersistTaps="handled"
contentContainerStyle={styles.body}>
<View style={styles.bodyContent}>
{/*STEP 1: Get phoneNumber and send to backend for OTP code*/}
{step == 1 && (
<View>
<View>
<Text style={styles.headerstyle}>
What is your phone number
</Text>
</View>
<View style={{flexDirection: 'row'}}>
<Input
placeholder={'Code'}
icon="deskphone"
value={countryCode}
keyboardType="decimal-pad"
maxLength={4}
onChangeText={c => setCountryCode(c)}
style={styles.codeInput}
/>
<Input
placeholder={'What is your phone number'}
icon="deskphone"
value={phoneNumber}
keyboardType="decimal-pad"
onChangeText={phoneNumber => setPhoneNumber(phoneNumber)}
style={styles.codePhoneInput}
/>
</View>
<TouchableOpacity
disabled={loading}
onPress={signInWithPhoneNumber}
style={styles.buttonstyle}>
{loading ? (
<ActivityIndicator size={23} color={theme.PRIMARY_COLOR} />
) : (
<Text style={styles.buttontextstyle}>
Request confirmation code
</Text>
)}
</TouchableOpacity>
</View>
)}
{/*STEP 2: Listen to the otp code */}
{step == 2 && (
<View>
<View>
<Text style={styles.subText}>
You should receive an sms with a confirmation code shortly.
</Text>
</View>
<View>
<Text style={styles.headerstyle}>Input OTP code</Text>
</View>
{invalidCodeError ? (
<Text style={styles.invalidCodeError}>{invalidCodeError}</Text>
) : null}
<Input
placeholder={'Input OTP Code'}
icon="deskphone"
value={code}
keyboardType="numeric"
maxLength={6}
onChangeText={code => setCode(code)}
/>
<TouchableOpacity
onPress={confirmCode}
style={styles.buttonstyle}>
{loading ? (
<ActivityIndicator size={23} color={theme.PRIMARY_COLOR} />
) : (
<Text style={styles.buttontextstyle}>
{'Verify OTP code'}
</Text>
)}
</TouchableOpacity>
</View>
)}
{/*STEP 3: Receive other user information*/}
{step == 3 && (
<View>
<View>
<Text style={styles.subText}>Lets know you</Text>
</View>
<View>
<Text style={styles.headerstyle}>Personal information</Text>
</View>
<Input
placeholder={'Your name'}
icon="account-outline"
value={name}
onChangeText={name => setName(name)}
/>
<Input
placeholder={'Your email'}
icon="email-outline"
keyboardType="email-address"
autoCapitalize="none"
value={email}
onChangeText={email => setEmail(email)}
/>
<Input
secureTextEntry
placeholder={'Choose a password'}
icon="lock-outline"
value={password}
onChangeText={password => setPassword(password)}
passwordiconlocked="eye-outline"
passwordiconunlocked="eye-off-outline"
/>
<Input
secureTextEntry
placeholder={'Re-enter the password'}
icon="lock-outline"
value={passwordConfirmation}
onChangeText={pwd => setPasswordConfirmation(pwd)}
passwordiconlocked="eye-outline"
passwordiconunlocked="eye-off-outline"
/>
<TouchableOpacity
onPress={completeRegistration}
style={styles.buttonstyle}>
{loading ? (
<ActivityIndicator size={23} color={theme.PRIMARY_COLOR} />
) : (
<Text style={styles.buttontextstyle}>Submit</Text>
)}
</TouchableOpacity>
</View>
)}
</View>
</ScrollView>
</MyPage>
);
};
export default PhoneAuth;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment