import React, {useState, useRef} from 'react'; import {RouteProp} from '@react-navigation/native'; import {StackNavigationProp} from '@react-navigation/stack'; import { View, Text, StyleSheet, StatusBar, Alert, Platform, TouchableOpacity, KeyboardAvoidingView, ActivityIndicator, } from 'react-native'; import {usePromiseTracker, trackPromise} from 'react-promise-tracker'; import {RootStackParamList} from '../../routes'; import { ArrowButton, RegistrationWizard, TaggInput, TermsConditions, Background, } from '../../components'; import { emailRegex, passwordRegex, usernameRegex, REGISTER_ENDPOINT, SEND_OTP_ENDPOINT, } from '../../constants'; type RegistrationScreenRouteProp = RouteProp< RootStackParamList, 'Registration' >; type RegistrationScreenNavigationProp = StackNavigationProp< RootStackParamList, 'Registration' >; interface RegistrationProps { route: RegistrationScreenRouteProp; navigation: RegistrationScreenNavigationProp; } /** * Registration screen. * @param navigation react-navigation navigation object */ const Registration: React.FC = ({navigation}) => { // refs for changing focus const lnameRef = useRef(); const emailRef = useRef(); const usernameRef = useRef(); const passwordRef = useRef(); const confirmRef = useRef(); /** * Handles focus change to the next input field. * @param field key for field to move focus onto */ const handleFocusChange = (field: string): void => { switch (field) { case 'lname': const lnameField: any = lnameRef.current; lnameField.focus(); break; case 'email': const emailField: any = emailRef.current; emailField.focus(); break; case 'username': const usernameField: any = usernameRef.current; usernameField.focus(); break; case 'password': const passwordField: any = passwordRef.current; passwordField.focus(); break; case 'confirm': const confirmField: any = confirmRef.current; confirmField.focus(); break; default: return; } }; // registration form state const [form, setForm] = useState({ fname: '', lname: '', email: '', username: '', password: '', confirm: '', isValidFname: false, isValidLname: false, isValidEmail: false, isValidUsername: false, isValidPassword: false, passwordsMatch: false, tcAccepted: false, attemptedSubmit: false, }); /* * Handles changes to the first name field value and verifies the input by updating state and running a validation function. */ const handleFnameUpdate = (fname: string) => { let isValidFname: boolean = fname.length > 0; setForm({ ...form, fname, isValidFname, }); }; /* * Handles changes to the last name field value and verifies the input by updating state and running a validation function. */ const handleLnameUpdate = (lname: string) => { let isValidLname: boolean = lname.length > 0; setForm({ ...form, lname, isValidLname, }); }; /* * Handles changes to the email field value and verifies the input by updating state and running a validation function. */ const handleEmailUpdate = (email: string) => { let isValidEmail: boolean = emailRegex.test(email); setForm({ ...form, email, isValidEmail, }); }; /* * Handles changes to the username field value and verifies the input by updating state and running a validation function. */ const handleUsernameUpdate = (username: string) => { let isValidUsername: boolean = usernameRegex.test(username); setForm({ ...form, username, isValidUsername, }); }; /* * Handles changes to the password field value and verifies the input by updating state and running a validation function. */ const handlePasswordUpdate = (password: string) => { let isValidPassword: boolean = passwordRegex.test(password); let passwordsMatch: boolean = form.password === form.confirm; setForm({ ...form, password, isValidPassword, passwordsMatch, }); }; /* * Handles changes to the confirm password field value and verifies the input by updating state and running a validation function. */ const handleConfirmUpdate = (confirm: string) => { let passwordsMatch: boolean = form.password === confirm; setForm({ ...form, confirm, passwordsMatch, }); }; /** * Handles changes to the terms and conditions accepted boolean. * @param tcAccepted the boolean to set the terms and conditions value to */ const handleTcUpdate = (tcAccepted: boolean) => { setForm({ ...form, tcAccepted, }); }; /** * Handles a click on the "next" arrow button by sending an API request to the backend and displaying the appropriate response. */ const handleRegister = async () => { if (!form.attemptedSubmit) { setForm({ ...form, attemptedSubmit: true, }); } try { if ( form.isValidFname && form.isValidLname && form.isValidUsername && form.isValidPassword && form.passwordsMatch ) { if (form.tcAccepted) { let registerResponse = await fetch(REGISTER_ENDPOINT, { method: 'POST', body: JSON.stringify({ first_name: form.fname, last_name: form.lname, email: form.email, username: form.username, password: form.password, }), }); let statusCode = registerResponse.status; let data = await registerResponse.json(); if (statusCode === 201) { let sendOtpResponse = await trackPromise( fetch(SEND_OTP_ENDPOINT, { method: 'POST', body: JSON.stringify({ username: form.username, email: form.email, }), }), ); let otpStatusCode = sendOtpResponse.status; if (otpStatusCode === 200) { const userId: string = data.UserID; navigation.navigate('Verification', { username: form.username, email: form.email, userId: userId, }); } } else if (statusCode === 409) { Alert.alert('Registration failed ๐Ÿ˜”', `${data}`); } else { Alert.alert( 'Something went wrong! ๐Ÿ˜ญ', "Would you believe me if I told you that I don't know what happened?", ); } } else { Alert.alert( 'Terms and conditions', 'You must first agree to the terms and conditions.', ); } } else { setForm({...form, attemptedSubmit: false}); setTimeout(() => setForm({...form, attemptedSubmit: true})); } } catch (error) { Alert.alert( 'Registration failed ๐Ÿ˜“', 'Please double-check your network connection and retry.', ); return { name: 'Registration error', description: error, }; } }; const Footer = () => ( navigation.navigate('Login')} /> ); /** * An activity indicator to indicate that the app is working during the send_otp request. */ const LoadingIndicator = () => { const {promiseInProgress} = usePromiseTracker(); return promiseInProgress ? ( ) : ( <> ); }; return ( Sign up. handleFocusChange('lname')} blurOnSubmit={false} valid={form.isValidFname} invalidWarning="First name cannot be empty." attemptedSubmit={form.attemptedSubmit} width={280} /> handleFocusChange('email')} blurOnSubmit={false} ref={lnameRef} valid={form.isValidLname} invalidWarning="Last name cannot be empty." attemptedSubmit={form.attemptedSubmit} width={280} /> handleFocusChange('username')} blurOnSubmit={false} ref={emailRef} valid={form.isValidEmail} invalidWarning={'Please enter a valid email address.'} attemptedSubmit={form.attemptedSubmit} width={280} /> handleFocusChange('password')} blurOnSubmit={false} ref={usernameRef} valid={form.isValidUsername} invalidWarning={ 'Username must beย at least 6 characters and contain only alphanumerics.' } attemptedSubmit={form.attemptedSubmit} width={280} /> handleFocusChange('confirm')} blurOnSubmit={false} secureTextEntry ref={passwordRef} valid={form.isValidPassword} invalidWarning={ 'Password must be at least 8 characters & contain at least one of a-z, A-Z, 0-9, and a special character.' } attemptedSubmit={form.attemptedSubmit} width={280} />