Skip to content

Commit

Permalink
Merge pull request Expensify#32196 from software-mansion-labs/form-mi…
Browse files Browse the repository at this point in the history
…gration/idology-questions-fix

[Form Provider Refactor] IdologyQuestions fixes
  • Loading branch information
luacmartins authored Nov 29, 2023
2 parents 03386f5 + 92fcded commit ee92da0
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 65 deletions.
4 changes: 2 additions & 2 deletions src/components/FixedFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ type FixedFooterProps = {
children: ReactNode;

/** Styles to be assigned to Container */
style: Array<StyleProp<ViewStyle>>;
style?: StyleProp<ViewStyle>;
};

function FixedFooter({style = [], children}: FixedFooterProps) {
const styles = useThemeStyles();
return <View style={[styles.ph5, styles.pb5, styles.flexShrink0, ...style]}>{children}</View>;
return <View style={[styles.ph5, styles.pb5, styles.flexShrink0, style]}>{children}</View>;
}

FixedFooter.displayName = 'FixedFooter';
Expand Down
3 changes: 2 additions & 1 deletion src/components/Form/FormWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton';
import FormSubmit from '@components/FormSubmit';
import refPropTypes from '@components/refPropTypes';
import SafeAreaConsumer from '@components/SafeAreaConsumer';
import ScrollViewWithContext from '@components/ScrollViewWithContext';
import * as ErrorUtils from '@libs/ErrorUtils';
Expand Down Expand Up @@ -64,7 +65,7 @@ const propTypes = {

errors: errorsPropType.isRequired,

inputRefs: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object])).isRequired,
inputRefs: PropTypes.objectOf(refPropTypes).isRequired,
};

const defaultProps = {
Expand Down
6 changes: 3 additions & 3 deletions src/components/RadioButtons.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React, {useState} from 'react';
import {View} from 'react-native';
import useThemeStyles from '@styles/useThemeStyles';
import RadioButtonWithLabel from './RadioButtonWithLabel';

Expand All @@ -21,7 +20,7 @@ function RadioButtons({items, onPress}: RadioButtonsProps) {
const [checkedValue, setCheckedValue] = useState('');

return (
<View>
<>
{items.map((item) => (
<RadioButtonWithLabel
key={item.value}
Expand All @@ -34,10 +33,11 @@ function RadioButtons({items, onPress}: RadioButtonsProps) {
label={item.label}
/>
))}
</View>
</>
);
}

RadioButtons.displayName = 'RadioButtons';

export type {Choice};
export default RadioButtons;
39 changes: 39 additions & 0 deletions src/components/SingleChoiceQuestion.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React, {ForwardedRef, forwardRef} from 'react';
import {Text as RNText} from 'react-native';
import useThemeStyles from '@styles/useThemeStyles';
import FormHelpMessage from './FormHelpMessage';
import RadioButtons, {Choice} from './RadioButtons';
import Text from './Text';

type SingleChoiceQuestionProps = {
prompt: string;
errorText?: string | string[];
possibleAnswers: Choice[];
currentQuestionIndex: number;
onInputChange: (value: string) => void;
};

function SingleChoiceQuestion({prompt, errorText, possibleAnswers, currentQuestionIndex, onInputChange}: SingleChoiceQuestionProps, ref: ForwardedRef<RNText>) {
const styles = useThemeStyles();

return (
<>
<Text
ref={ref}
style={[styles.textStrong, styles.mb5]}
>
{prompt}
</Text>
<RadioButtons
items={possibleAnswers}
key={currentQuestionIndex}
onPress={onInputChange}
/>
<FormHelpMessage message={errorText} />
</>
);
}

SingleChoiceQuestion.displayName = 'SingleChoiceQuestion';

export default forwardRef(SingleChoiceQuestion);
109 changes: 50 additions & 59 deletions src/pages/EnablePayments/IdologyQuestions.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import PropTypes from 'prop-types';
import React, {useRef, useState} from 'react';
import React, {useState} from 'react';
import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
import FixedFooter from '@components/FixedFooter';
import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton';
import FormScrollView from '@components/FormScrollView';
import OfflineIndicator from '@components/OfflineIndicator';
import RadioButtons from '@components/RadioButtons';
import FormProvider from '@components/Form/FormProvider';
import InputWrapper from '@components/Form/InputWrapper';
import SingleChoiceQuestion from '@components/SingleChoiceQuestion';
import Text from '@components/Text';
import TextLink from '@components/TextLink';
import useLocalize from '@hooks/useLocalize';
import * as ErrorUtils from '@libs/ErrorUtils';
import useThemeStyles from '@styles/useThemeStyles';
import * as BankAccounts from '@userActions/BankAccounts';
import ONYXKEYS from '@src/ONYXKEYS';
Expand Down Expand Up @@ -51,15 +48,13 @@ const defaultProps = {
walletAdditionalDetails: {},
};

function IdologyQuestions({questions, walletAdditionalDetails, idNumber}) {
function IdologyQuestions({questions, idNumber}) {
const styles = useThemeStyles();
const formRef = useRef();
const {translate} = useLocalize();

const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
const [shouldHideSkipAnswer, setShouldHideSkipAnswer] = useState(false);
const [userAnswers, setUserAnswers] = useState([]);
const [error, setError] = useState('');

const currentQuestion = questions[currentQuestionIndex] || {};
const possibleAnswers = _.filter(
Expand All @@ -74,7 +69,6 @@ function IdologyQuestions({questions, walletAdditionalDetails, idNumber}) {
};
}),
);
const errorMessage = ErrorUtils.getLatestErrorMessage(walletAdditionalDetails) || error;

/**
* Put question answer in the state.
Expand All @@ -86,40 +80,46 @@ function IdologyQuestions({questions, walletAdditionalDetails, idNumber}) {
tempAnswers[currentQuestionIndex] = {question: currentQuestion.type, answer};

setUserAnswers(tempAnswers);
setError('');
};

/**
* Show next question or send all answers for Idology verifications when we've answered enough
*/
const submitAnswers = () => {
if (!userAnswers[currentQuestionIndex]) {
setError(translate('additionalDetailsStep.selectAnswer'));
} else {
// Get the number of questions that were skipped by the user.
const skippedQuestionsCount = _.filter(userAnswers, (answer) => answer.answer === SKIP_QUESTION_TEXT).length;

// We have enough answers, let's call expectID KBA to verify them
if (userAnswers.length - skippedQuestionsCount >= questions.length - MAX_SKIP) {
const tempAnswers = _.map(userAnswers, _.clone);

// Auto skip any remaining questions
if (tempAnswers.length < questions.length) {
for (let i = tempAnswers.length; i < questions.length; i++) {
tempAnswers[i] = {question: questions[i].type, answer: SKIP_QUESTION_TEXT};
}
}
return;
}
// Get the number of questions that were skipped by the user.
const skippedQuestionsCount = _.filter(userAnswers, (answer) => answer.answer === SKIP_QUESTION_TEXT).length;

BankAccounts.answerQuestionsForWallet(tempAnswers, idNumber);
setUserAnswers(tempAnswers);
} else {
// Else, show next question
setCurrentQuestionIndex(currentQuestionIndex + 1);
setShouldHideSkipAnswer(skippedQuestionsCount >= MAX_SKIP);
// We have enough answers, let's call expectID KBA to verify them
if (userAnswers.length - skippedQuestionsCount >= questions.length - MAX_SKIP) {
const tempAnswers = _.map(userAnswers, _.clone);

// Auto skip any remaining questions
if (tempAnswers.length < questions.length) {
for (let i = tempAnswers.length; i < questions.length; i++) {
tempAnswers[i] = {question: questions[i].type, answer: SKIP_QUESTION_TEXT};
}
}

BankAccounts.answerQuestionsForWallet(tempAnswers, idNumber);
setUserAnswers(tempAnswers);
} else {
// Else, show next question
setCurrentQuestionIndex(currentQuestionIndex + 1);
setShouldHideSkipAnswer(skippedQuestionsCount >= MAX_SKIP);
}
};

const validate = (values) => {
const errors = {};
if (!values.answer) {
errors.answer = translate('additionalDetailsStep.selectAnswer');
}
return errors;
};

return (
<View style={styles.flex1}>
<View style={styles.ph5}>
Expand All @@ -131,33 +131,24 @@ function IdologyQuestions({questions, walletAdditionalDetails, idNumber}) {
{translate('additionalDetailsStep.helpLink')}
</TextLink>
</View>
<FormScrollView ref={formRef}>
<View
style={styles.m5}
key={currentQuestion.type}
>
<Text style={[styles.textStrong, styles.mb5]}>{currentQuestion.prompt}</Text>
<RadioButtons
items={possibleAnswers}
key={currentQuestionIndex}
onPress={chooseAnswer}
/>
</View>
</FormScrollView>
<FixedFooter>
<FormAlertWithSubmitButton
isAlertVisible={Boolean(errorMessage)}
onSubmit={submitAnswers}
onFixTheErrorsLinkPressed={() => {
formRef.current.scrollTo({y: 0, animated: true});
}}
message={errorMessage}
isLoading={walletAdditionalDetails.isLoading}
buttonText={translate('common.saveAndContinue')}
containerStyles={[styles.mh0, styles.mv0, styles.mb0]}
<FormProvider
formID={ONYXKEYS.WALLET_ADDITIONAL_DETAILS}
onSubmit={submitAnswers}
key={currentQuestionIndex}
validate={validate}
scrollContextEnabled
style={[styles.flexGrow1, styles.ph5]}
submitButtonText={translate('common.saveAndContinue')}
>
<InputWrapper
InputComponent={SingleChoiceQuestion}
inputID="answer"
prompt={currentQuestion.prompt}
possibleAnswers={possibleAnswers}
currentQuestionIndex={currentQuestionIndex}
onValueChange={chooseAnswer}
/>
<OfflineIndicator containerStyles={[styles.mh5, styles.mb3]} />
</FixedFooter>
</FormProvider>
</View>
);
}
Expand Down

0 comments on commit ee92da0

Please sign in to comment.