From e32241734c8cc258812ac12c7727aaa7f947eed5 Mon Sep 17 00:00:00 2001 From: Leon Jiang <35908040+leonyjiang@users.noreply.github.com> Date: Wed, 8 Jul 2020 09:56:17 -0700 Subject: [TMA-60] Registration Page UI & Field Validation (#13) * remove unused image * refactor LoginInput component to be more generic * configure bare registration screen * create index files for exports * add yarn typing script * refactor and re-style LoginInput component * re-style login screen according to designs * make LoginInput name more generic, give TaggInput dirty & width props * add disabled feature to login screen submit button, finalized styles * add arrow images and create ArrowButton component * create RegistrationWizard component and move files around * added disabled & enabled buttons to ArrowButton component * create dummy terms and conditions text * create common CenteredView component for re-use * create custom RadioCheckbox for registration screen * create TermsConditions & OverlayView components * update index.ts export files * build registration page UI with basic validation * yarn lint/type & add platform-specific styling * add yarn type item to PR checklist * add react-native-animatable dependency to project * add regex variables to constants file * Add width prop for more flexible styling * Add types and disable auto-capitalization * Update email validation regex * Create linear-gradient background component * Update password regex and add inline docs * Refactor code to be more readable * Add warning prop and animation to TaggInput * Add wrapper View for vertical margins * Make JSX more readable & add TaggInput components * Integrate refactored code into registration page * Merge in login screen changes * Lint and fix file syntax * Fix function docs * Add ViewProps to CenterView props * Add KeyboardAvoidingView to Background component * Add blurOnSubmit for inputs, restore deleted handleLogin code * Create Verification screen and add it to routes * Add routing to Verification page upon success * Add API request upon registration submit * Trigger warning shaking animation on submit * Make disabled arrow touchable, tap triggers submit --- src/assets/images/arrow-backward.png | Bin 0 -> 430 bytes src/assets/images/arrow-backward@2x.png | Bin 0 -> 709 bytes src/assets/images/arrow-backward@3x.png | Bin 0 -> 983 bytes src/assets/images/arrow-forward-disabled.png | Bin 0 -> 499 bytes src/assets/images/arrow-forward-disabled@2x.png | Bin 0 -> 1138 bytes src/assets/images/arrow-forward-disabled@3x.png | Bin 0 -> 1376 bytes src/assets/images/arrow-forward-enabled.png | Bin 0 -> 437 bytes src/assets/images/arrow-forward-enabled@2x.png | Bin 0 -> 784 bytes src/assets/images/arrow-forward-enabled@3x.png | Bin 0 -> 1024 bytes src/components/common/CenteredView.tsx | 25 ++ src/components/common/LoginInput.tsx | 105 ------ src/components/common/OverlayView.tsx | 19 + src/components/common/RadioCheckbox.tsx | 40 ++ src/components/common/TaggInput.tsx | 62 ++++ src/components/common/index.ts | 4 + src/components/index.ts | 2 + src/components/onboarding/ArrowButton.tsx | 23 ++ src/components/onboarding/Background.tsx | 44 +++ src/components/onboarding/RegistrationWizard.tsx | 47 +++ src/components/onboarding/TermsConditions.tsx | 140 +++++++ src/components/onboarding/index.ts | 4 + src/constants/api.ts | 4 + src/constants/index.ts | 8 +- src/constants/regex.ts | 21 ++ src/constants/termsConditions.ts | 11 + src/routes/Routes.tsx | 12 +- src/screens/Login.tsx | 325 +++++++++------- src/screens/Registration.tsx | 448 ++++++++++++++++++++++- src/screens/Verification.tsx | 28 ++ src/screens/index.ts | 1 + 30 files changed, 1108 insertions(+), 265 deletions(-) create mode 100644 src/assets/images/arrow-backward.png create mode 100644 src/assets/images/arrow-backward@2x.png create mode 100644 src/assets/images/arrow-backward@3x.png create mode 100644 src/assets/images/arrow-forward-disabled.png create mode 100644 src/assets/images/arrow-forward-disabled@2x.png create mode 100644 src/assets/images/arrow-forward-disabled@3x.png create mode 100644 src/assets/images/arrow-forward-enabled.png create mode 100644 src/assets/images/arrow-forward-enabled@2x.png create mode 100644 src/assets/images/arrow-forward-enabled@3x.png create mode 100644 src/components/common/CenteredView.tsx delete mode 100644 src/components/common/LoginInput.tsx create mode 100644 src/components/common/OverlayView.tsx create mode 100644 src/components/common/RadioCheckbox.tsx create mode 100644 src/components/common/TaggInput.tsx create mode 100644 src/components/common/index.ts create mode 100644 src/components/index.ts create mode 100644 src/components/onboarding/ArrowButton.tsx create mode 100644 src/components/onboarding/Background.tsx create mode 100644 src/components/onboarding/RegistrationWizard.tsx create mode 100644 src/components/onboarding/TermsConditions.tsx create mode 100644 src/components/onboarding/index.ts create mode 100644 src/constants/api.ts create mode 100644 src/constants/regex.ts create mode 100644 src/constants/termsConditions.ts create mode 100644 src/screens/Verification.tsx (limited to 'src') diff --git a/src/assets/images/arrow-backward.png b/src/assets/images/arrow-backward.png new file mode 100644 index 00000000..b6da5b48 Binary files /dev/null and b/src/assets/images/arrow-backward.png differ diff --git a/src/assets/images/arrow-backward@2x.png b/src/assets/images/arrow-backward@2x.png new file mode 100644 index 00000000..cf09be25 Binary files /dev/null and b/src/assets/images/arrow-backward@2x.png differ diff --git a/src/assets/images/arrow-backward@3x.png b/src/assets/images/arrow-backward@3x.png new file mode 100644 index 00000000..7e98f51b Binary files /dev/null and b/src/assets/images/arrow-backward@3x.png differ diff --git a/src/assets/images/arrow-forward-disabled.png b/src/assets/images/arrow-forward-disabled.png new file mode 100644 index 00000000..b1141aa6 Binary files /dev/null and b/src/assets/images/arrow-forward-disabled.png differ diff --git a/src/assets/images/arrow-forward-disabled@2x.png b/src/assets/images/arrow-forward-disabled@2x.png new file mode 100644 index 00000000..46680148 Binary files /dev/null and b/src/assets/images/arrow-forward-disabled@2x.png differ diff --git a/src/assets/images/arrow-forward-disabled@3x.png b/src/assets/images/arrow-forward-disabled@3x.png new file mode 100644 index 00000000..d079e693 Binary files /dev/null and b/src/assets/images/arrow-forward-disabled@3x.png differ diff --git a/src/assets/images/arrow-forward-enabled.png b/src/assets/images/arrow-forward-enabled.png new file mode 100644 index 00000000..2d34c78d Binary files /dev/null and b/src/assets/images/arrow-forward-enabled.png differ diff --git a/src/assets/images/arrow-forward-enabled@2x.png b/src/assets/images/arrow-forward-enabled@2x.png new file mode 100644 index 00000000..b97a2315 Binary files /dev/null and b/src/assets/images/arrow-forward-enabled@2x.png differ diff --git a/src/assets/images/arrow-forward-enabled@3x.png b/src/assets/images/arrow-forward-enabled@3x.png new file mode 100644 index 00000000..7a658e4b Binary files /dev/null and b/src/assets/images/arrow-forward-enabled@3x.png differ diff --git a/src/components/common/CenteredView.tsx b/src/components/common/CenteredView.tsx new file mode 100644 index 00000000..1c5ed399 --- /dev/null +++ b/src/components/common/CenteredView.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import {View, StyleSheet, ViewProps} from 'react-native'; + +interface CenteredViewProps extends ViewProps {} +/** + * A centered view that grows to its parents size. + * @param children - children of this component. + */ +const CenteredView: React.FC = (props) => { + return ( + + {props.children} + + ); +}; + +const styles = StyleSheet.create({ + centeredView: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, +}); + +export default CenteredView; diff --git a/src/components/common/LoginInput.tsx b/src/components/common/LoginInput.tsx deleted file mode 100644 index 2a1768a7..00000000 --- a/src/components/common/LoginInput.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import React from 'react'; -import {TextInput, StyleSheet} from 'react-native'; -import * as Animatable from 'react-native-animatable'; - -const LoginInput = (props: LoginInputProps) => { - return ( - <> - props.onChangeText(input)} - defaultValue={props.type} - onSubmitEditing={props.onSubmitEditing} - blurOnSubmit={ - props.isUsername ? false : props.isPassword ? undefined : undefined - } - secureTextEntry={ - props.isUsername ? false : props.isPassword ? true : false - } - ref={props.input_ref} - /> - {props.attempt_submit && !props.isValid && ( - - {props.validationWarning} - - )} - - ); -}; - -const styles = StyleSheet.create({ - credentials: { - top: 175, - width: 248, - height: 40, - fontSize: 20, - color: '#FFFFFF', - borderColor: '#FFFDFD', - borderWidth: 2, - borderRadius: 20, - paddingLeft: 13, - marginVertical: 15, - }, - invalidCredentials: { - top: 165, - color: '#F4DDFF', - paddingHorizontal: 30, - textAlign: 'center', - }, -}); - -interface LoginInputProps { - type: string; - isUsername?: boolean; - isPassword?: boolean; - onChangeText: (input: string) => void; - onSubmitEditing?: () => void; - attempt_submit?: boolean; - input_ref?: object; - isValid?: boolean; - validationWarning?: string; -} - -export default LoginInput; diff --git a/src/components/common/OverlayView.tsx b/src/components/common/OverlayView.tsx new file mode 100644 index 00000000..f0660614 --- /dev/null +++ b/src/components/common/OverlayView.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import {View, StyleSheet} from 'react-native'; + +/** + * A blurred & darkened view that grows to its parents size. Designed to be used with overlaid components. + * @param children - children of this component. + */ +const OverlayView: React.FC = ({children}) => { + return {children}; +}; + +const styles = StyleSheet.create({ + overlayView: { + flex: 1, + backgroundColor: '#00000080', + }, +}); + +export default OverlayView; diff --git a/src/components/common/RadioCheckbox.tsx b/src/components/common/RadioCheckbox.tsx new file mode 100644 index 00000000..33d50527 --- /dev/null +++ b/src/components/common/RadioCheckbox.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { + View, + StyleSheet, + TouchableOpacity, + TouchableOpacityProps, +} from 'react-native'; + +interface RadioCheckboxProps extends TouchableOpacityProps { + checked: boolean; +} +const RadioCheckbox: React.FC = (props) => { + return ( + + + {props.checked && } + + + ); +}; + +const styles = StyleSheet.create({ + outerCircle: { + width: 23, + height: 23, + borderRadius: 11.5, + borderWidth: 1, + borderColor: '#fff', + alignItems: 'center', + justifyContent: 'center', + }, + innerCircle: { + width: 17, + height: 17, + borderRadius: 8.5, + backgroundColor: '#04ffff', + }, +}); + +export default RadioCheckbox; diff --git a/src/components/common/TaggInput.tsx b/src/components/common/TaggInput.tsx new file mode 100644 index 00000000..fe11d4f0 --- /dev/null +++ b/src/components/common/TaggInput.tsx @@ -0,0 +1,62 @@ +import React from 'react'; +import {View, TextInput, StyleSheet, TextInputProps} from 'react-native'; +import * as Animatable from 'react-native-animatable'; + +interface TaggInputProps extends TextInputProps { + valid?: boolean; + invalidWarning?: string; + attemptedSubmit?: boolean; + width?: number | string; +} +/** + * An input component that receives all props a normal TextInput component does. TaggInput components grow to 60% of their parent's width by default, but this can be set using the `width` prop. + */ +const TaggInput = React.forwardRef((props: TaggInputProps, ref: any) => { + return ( + + + {props.attemptedSubmit && !props.valid && ( + + {props.invalidWarning} + + )} + + ); +}); + +const styles = StyleSheet.create({ + container: { + width: '100%', + alignItems: 'center', + marginVertical: 11, + }, + input: { + minWidth: '60%', + height: 40, + fontSize: 16, + fontWeight: '600', + color: '#fff', + borderColor: '#fffdfd', + borderWidth: 2, + borderRadius: 20, + paddingLeft: 13, + }, + warning: { + fontSize: 14, + marginTop: 5, + color: '#f4ddff', + maxWidth: 350, + textAlign: 'center', + }, +}); + +export default TaggInput; diff --git a/src/components/common/index.ts b/src/components/common/index.ts new file mode 100644 index 00000000..b7041b6d --- /dev/null +++ b/src/components/common/index.ts @@ -0,0 +1,4 @@ +export {default as CenteredView} from './CenteredView'; +export {default as OverlayView} from './OverlayView'; +export {default as RadioCheckbox} from './RadioCheckbox'; +export {default as TaggInput} from './TaggInput'; diff --git a/src/components/index.ts b/src/components/index.ts new file mode 100644 index 00000000..724b14ac --- /dev/null +++ b/src/components/index.ts @@ -0,0 +1,2 @@ +export * from './common'; +export * from './onboarding'; diff --git a/src/components/onboarding/ArrowButton.tsx b/src/components/onboarding/ArrowButton.tsx new file mode 100644 index 00000000..bf07c6ac --- /dev/null +++ b/src/components/onboarding/ArrowButton.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import {Image, TouchableOpacity, TouchableOpacityProps} from 'react-native'; + +interface ArrowButtonProps extends TouchableOpacityProps { + direction: 'forward' | 'backward'; + disabled?: boolean; +} +const ArrowButton: React.FC = (props: ArrowButtonProps) => { + const arrow = + props.direction === 'forward' + ? props.disabled + ? require('../../assets/images/arrow-forward-disabled.png') + : require('../../assets/images/arrow-forward-enabled.png') + : require('../../assets/images/arrow-backward.png'); + + return ( + + + + ); +}; + +export default ArrowButton; diff --git a/src/components/onboarding/Background.tsx b/src/components/onboarding/Background.tsx new file mode 100644 index 00000000..98082022 --- /dev/null +++ b/src/components/onboarding/Background.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import LinearGradient from 'react-native-linear-gradient'; +import { + StyleSheet, + TouchableWithoutFeedback, + Keyboard, + ViewProps, + KeyboardAvoidingView, + View, + Platform, +} from 'react-native'; + +interface BackgroundProps extends ViewProps {} +const Background: React.FC = (props) => { + return ( + + + + + {props.children} + + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + view: { + alignItems: 'center', + }, +}); + +export default Background; diff --git a/src/components/onboarding/RegistrationWizard.tsx b/src/components/onboarding/RegistrationWizard.tsx new file mode 100644 index 00000000..5d7e6ee2 --- /dev/null +++ b/src/components/onboarding/RegistrationWizard.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import {View, StyleSheet, ViewProps} from 'react-native'; + +interface RegistrationWizardProps extends ViewProps { + step: 'one' | 'two' | 'three'; +} + +const RegistrationWizard = (props: RegistrationWizardProps) => { + const stepStyle = styles.step; + const stepActiveStyle = [styles.step, styles.stepActive]; + return ( + + + + + + + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + }, + step: { + height: 18, + width: 18, + borderRadius: 9, + borderWidth: 2, + borderColor: '#e1f0ff', + }, + stepActive: { + backgroundColor: '#e1f0ff', + }, + progress: { + width: '30%', + height: 2, + backgroundColor: '#e1f0ff', + }, +}); + +export default RegistrationWizard; diff --git a/src/components/onboarding/TermsConditions.tsx b/src/components/onboarding/TermsConditions.tsx new file mode 100644 index 00000000..5af1b972 --- /dev/null +++ b/src/components/onboarding/TermsConditions.tsx @@ -0,0 +1,140 @@ +import React, {useState} from 'react'; +import { + Modal, + StyleSheet, + View, + Text, + Button, + TouchableOpacity, + ScrollView, + ViewProps, +} from 'react-native'; + +import {RadioCheckbox, CenteredView, OverlayView} from '../common'; +import {dummyTermsConditions} from '../../constants'; + +interface TermsConditionsProps extends ViewProps { + accepted: boolean; + onChange: (accepted: boolean) => void; +} +const TermsConditions: React.FC = (props) => { + // boolean representing if modal is visible + const [modalVisible, setModalVisible] = useState(false); + // destructure props + const {accepted, onChange} = props; + /** + * Hides the modal. + */ + const hideModal = (): void => { + if (modalVisible) { + setModalVisible(false); + } + }; + /** + * Sets `accepted` to `true` and hides the modal. + */ + const handleAccept = (): void => { + onChange(true); + hideModal(); + }; + /** + * Toggles the value of `accepted`. + */ + const toggleAccepted = (): void => { + onChange(!accepted); + }; + + return ( + + + + + I accept the + setModalVisible(true)}> + + terms and conditions. + + + + + + + + + + Terms and Conditions + {dummyTermsConditions} + + +