aboutsummaryrefslogtreecommitdiff
path: root/src/screens/onboarding/RegistrationTwo.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/screens/onboarding/RegistrationTwo.tsx')
-rw-r--r--src/screens/onboarding/RegistrationTwo.tsx375
1 files changed, 375 insertions, 0 deletions
diff --git a/src/screens/onboarding/RegistrationTwo.tsx b/src/screens/onboarding/RegistrationTwo.tsx
new file mode 100644
index 00000000..09e217f6
--- /dev/null
+++ b/src/screens/onboarding/RegistrationTwo.tsx
@@ -0,0 +1,375 @@
+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 {
+ passwordRegex,
+ usernameRegex,
+ REGISTER_ENDPOINT,
+ SEND_OTP_ENDPOINT,
+} from '../../constants';
+
+type RegistrationScreenTwoRouteProp = RouteProp<
+ RootStackParamList,
+ 'RegistrationTwo'
+>;
+type RegistrationScreenTwoNavigationProp = StackNavigationProp<
+ RootStackParamList,
+ 'RegistrationTwo'
+>;
+interface RegistrationTwoProps {
+ route: RegistrationScreenTwoRouteProp;
+ navigation: RegistrationScreenTwoNavigationProp;
+}
+/**
+ * Registration screen 2 for email, username, password, and terms and conditions
+ * @param navigation react-navigation navigation object
+ */
+const RegistrationTwo: React.FC<RegistrationTwoProps> = ({
+ route,
+ navigation,
+}) => {
+ // refs for changing focus
+ const usernameRef = useRef();
+ const passwordRef = useRef();
+ const confirmRef = useRef();
+
+ const registrationName = route.params;
+ const fname: string = registrationName!.firstName;
+ const lname: string = registrationName!.lastName;
+ const email: string = registrationName!.email;
+ /**
+ * 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 '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({
+ email: '',
+ username: '',
+ password: '',
+ confirm: '',
+ isValidEmail: false,
+ isValidUsername: false,
+ isValidPassword: false,
+ passwordsMatch: false,
+ tcAccepted: false,
+ attemptedSubmit: false,
+ });
+
+ /*
+ * 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.isValidUsername && form.isValidPassword && form.passwordsMatch) {
+ if (form.tcAccepted) {
+ let registerResponse = await fetch(REGISTER_ENDPOINT, {
+ method: 'POST',
+ body: JSON.stringify({
+ first_name: fname,
+ last_name: lname,
+ email: 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: email,
+ }),
+ }),
+ );
+ let otpStatusCode = sendOtpResponse.status;
+ if (otpStatusCode === 200) {
+ const userId: string = data.UserID;
+ navigation.navigate('Verification', {
+ username: form.username,
+ email: 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 = () => (
+ <View style={styles.footer}>
+ <ArrowButton
+ direction="backward"
+ onPress={() => navigation.navigate('RegistrationOne')}
+ />
+ <TouchableOpacity onPress={handleRegister}>
+ <ArrowButton
+ direction="forward"
+ disabled={
+ !(
+ form.isValidUsername &&
+ form.isValidPassword &&
+ form.passwordsMatch &&
+ form.tcAccepted
+ )
+ }
+ onPress={handleRegister}
+ />
+ </TouchableOpacity>
+ </View>
+ );
+
+ /**
+ * An activity indicator to indicate that the app is working during the send_otp request.
+ */
+ const LoadingIndicator = () => {
+ const {promiseInProgress} = usePromiseTracker();
+ return promiseInProgress ? (
+ <ActivityIndicator style={styles.load} size="large" color="#fff" />
+ ) : (
+ <></>
+ );
+ };
+
+ return (
+ <Background style={styles.container}>
+ <StatusBar barStyle="light-content" />
+ <RegistrationWizard style={styles.wizard} step="two" />
+ <KeyboardAvoidingView
+ behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
+ style={styles.container}>
+ <View>
+ <Text style={styles.formHeader}>SIGN UP</Text>
+ </View>
+ <TaggInput
+ accessibilityHint="Enter a username."
+ accessibilityLabel="Username input field."
+ placeholder="Username"
+ autoCompleteType="username"
+ textContentType="username"
+ autoCapitalize="none"
+ returnKeyType="next"
+ onChangeText={handleUsernameUpdate}
+ onSubmitEditing={() => 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}
+ />
+ <TaggInput
+ accessibilityHint="Enter a password."
+ accessibilityLabel="Password input field."
+ placeholder="Password"
+ autoCompleteType="password"
+ textContentType="newPassword"
+ returnKeyType="next"
+ onChangeText={handlePasswordUpdate}
+ onSubmitEditing={() => 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}
+ />
+ <TaggInput
+ accessibilityHint={'Re-enter your password.'}
+ accessibilityLabel={'Password confirmation input field.'}
+ placeholder={'Confirm Password'}
+ autoCompleteType="password"
+ textContentType="password"
+ returnKeyType={form.tcAccepted ? 'go' : 'default'}
+ onChangeText={handleConfirmUpdate}
+ onSubmitEditing={handleRegister}
+ secureTextEntry
+ ref={confirmRef}
+ valid={form.passwordsMatch}
+ invalidWarning={'Passwords must match.'}
+ attemptedSubmit={form.attemptedSubmit}
+ width={280}
+ />
+ <LoadingIndicator />
+ <TermsConditions
+ style={styles.tc}
+ accepted={form.tcAccepted}
+ onChange={handleTcUpdate}
+ />
+ </KeyboardAvoidingView>
+ <Footer />
+ </Background>
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ wizard: {
+ ...Platform.select({
+ ios: {
+ top: 50,
+ },
+ android: {
+ bottom: 40,
+ },
+ }),
+ },
+ formHeader: {
+ color: '#fff',
+ fontSize: 30,
+ fontWeight: '600',
+ marginBottom: '16%',
+ },
+ tc: {
+ marginVertical: '5%',
+ top: '8%',
+ },
+ load: {
+ top: '5%',
+ },
+ footer: {
+ width: '100%',
+ flexDirection: 'row',
+ justifyContent: 'space-around',
+ ...Platform.select({
+ ios: {
+ bottom: '20%',
+ },
+ android: {
+ bottom: '10%',
+ },
+ }),
+ },
+});
+
+export default RegistrationTwo;