Skip to content

Commit

Permalink
feat: Change business type selector from dropdown to list (Expensify#13)
Browse files Browse the repository at this point in the history
* feat: Change business type selector from dropdown to list

* fix: typos

* fix: remove unnecessary draft

* fix: cr fixes
  • Loading branch information
MrMuzyk authored Jan 26, 2024
1 parent bc1784d commit f4991ba
Show file tree
Hide file tree
Showing 5 changed files with 185 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import IncorporationStateBusiness from './substeps/IncorporationStateBusiness';
import NameBusiness from './substeps/NameBusiness';
import PhoneNumberBusiness from './substeps/PhoneNumberBusiness';
import TaxIdBusiness from './substeps/TaxIdBusiness';
import TypeBusiness from './substeps/TypeBusiness';
import TypeBusiness from './substeps/TypeBusiness/TypeBusiness';
import WebsiteBusiness from './substeps/WebsiteBusiness';

type BusinessInfoOnyxProps = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import React, {useMemo} from 'react';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import Modal from '@components/Modal';
import ScreenWrapper from '@components/ScreenWrapper';
import SelectionList from '@components/SelectionList';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import CONST from '@src/CONST';
import type {BusinessTypeItemType, IncorporationType} from './types';

type BusinessTypeSelectorModalProps = {
/** Whether the modal is visible */
isVisible: boolean;

/** Business type value selected */
currentBusinessType: string;

/** Function to call when the user selects a business type */
onBusinessTypeSelected: (value: BusinessTypeItemType) => void;

/** Function to call when the user closes the business type selector modal */
onClose: () => void;

/** Label to display on field */
label: string;
};

function BusinessTypeSelectorModal({isVisible, currentBusinessType, onBusinessTypeSelected, onClose, label}: BusinessTypeSelectorModalProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();

const incorporationTypes = useMemo(
() =>
Object.keys(CONST.INCORPORATION_TYPES).map((key) => ({
value: key,
text: translate(`businessInfoStep.incorporationType.${key as IncorporationType}`),
keyForList: key,
isSelected: key === currentBusinessType,
})),
[currentBusinessType, translate],
);

return (
<Modal
type={CONST.MODAL.MODAL_TYPE.RIGHT_DOCKED}
isVisible={isVisible}
onClose={onClose}
onModalHide={onClose}
hideModalContentWhileAnimating
useNativeDriver
>
<ScreenWrapper
style={[styles.pb0]}
includePaddingTop={false}
includeSafeAreaPaddingBottom={false}
testID={BusinessTypeSelectorModal.displayName}
>
<HeaderWithBackButton
title={label}
shouldShowBackButton
onBackButtonPress={onClose}
/>
<SelectionList
headerMessage={translate('businessInfoStep.companyType')}
sections={[{data: incorporationTypes, indexOffset: 0}]}
initiallyFocusedOptionKey={currentBusinessType}
onSelectRow={onBusinessTypeSelected}
shouldStopPropagation
shouldUseDynamicMaxToRenderPerBatch
/>
</ScreenWrapper>
</Modal>
);
}

BusinessTypeSelectorModal.displayName = 'BusinessTypeSelectorModal';

export default BusinessTypeSelectorModal;
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import React, {useState} from 'react';
import type {StyleProp, ViewStyle} from 'react-native';
import {View} from 'react-native';
import FormHelpMessage from '@components/FormHelpMessage';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import BusinessTypeSelectorModal from './BusinessTypeSelectorModal';
import type {BusinessTypeItemType, IncorporationType} from './types';

type BusinessTypePickerProps = {
/** Error text to display */
errorText?: string;

/** Business type to display */
value: string;

/** Callback to call when the input changes */
onInputChange: (value: string) => void;

/** Label to display on field */
label: string;

/** Any additional styles to apply */
wrapperStyle: StyleProp<ViewStyle>;

/** Callback to call when the picker modal is dismissed */
onBlur: () => void;
};

function BusinessTypePicker({errorText = '', value, wrapperStyle, onInputChange, label, onBlur}: BusinessTypePickerProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();

const [isPickerVisible, setIsPickerVisible] = useState(false);

const showPickerModal = () => {
setIsPickerVisible(true);
};

const hidePickerModal = (shouldBlur = true) => {
if (shouldBlur) {
onBlur();
}
setIsPickerVisible(false);
};

const updateBusinessTypeInput = (businessTypeItem: BusinessTypeItemType) => {
if (businessTypeItem.value !== value) {
onInputChange(businessTypeItem.value);
}
// If the user selects any business type, call the hidePickerModal function with shouldBlur = false
// to prevent the onBlur function from being called.
hidePickerModal(false);
};

const title = value ? translate(`businessInfoStep.incorporationType.${value as IncorporationType}`) : '';
const descStyle = title.length === 0 ? styles.textNormal : null;

return (
<View>
<MenuItemWithTopDescription
shouldShowRightIcon
title={title}
description={label}
descriptionTextStyle={descStyle}
onPress={showPickerModal}
wrapperStyle={wrapperStyle}
/>
<View style={styles.ml5}>
<FormHelpMessage message={errorText} />
</View>
<BusinessTypeSelectorModal
isVisible={isPickerVisible}
currentBusinessType={value}
onClose={hidePickerModal}
onBusinessTypeSelected={updateBusinessTypeInput}
label={label}
/>
</View>
);
}

BusinessTypePicker.displayName = 'BusinessTypePicker';

export default BusinessTypePicker;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type CONST from '@src/CONST';

type IncorporationType = keyof typeof CONST.INCORPORATION_TYPES;

type BusinessTypeItemType = {
value: string;
text: string;
keyForList: string;
isSelected: boolean;
};

export type {IncorporationType, BusinessTypeItemType};
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
import FormProvider from '@components/Form/FormProvider';
import InputWrapper from '@components/Form/InputWrapper';
import Picker from '@components/Picker';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useReimbursementAccountStepFormSubmit from '@hooks/useReimbursementAccountStepFormSubmit';
Expand All @@ -15,6 +14,7 @@ import ONYXKEYS from '@src/ONYXKEYS';
import type {ReimbursementAccount} from '@src/types/onyx';
import type {FormValues} from '@src/types/onyx/Form';
import type * as OnyxCommon from '@src/types/onyx/OnyxCommon';
import BusinessTypePicker from './BusinessTypePicker';

type TypeBusinessOnyxProps = {
/** Reimbursement account from ONYX */
Expand All @@ -23,16 +23,14 @@ type TypeBusinessOnyxProps = {

type TypeBusinessProps = TypeBusinessOnyxProps & SubStepProps;

type IncorporationType = keyof typeof CONST.INCORPORATION_TYPES;

const COMPANY_INCORPORATION_TYPE_KEY = CONST.BANK_ACCOUNT.BUSINESS_INFO_STEP.INPUT_KEY.INCORPORATION_TYPE;
const STEP_FIELDS = [COMPANY_INCORPORATION_TYPE_KEY];

const validate = (values: FormValues): OnyxCommon.Errors => ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS);

function TypeBusiness({reimbursementAccount, onNext, isEditing}: TypeBusinessProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();
const validate = (values: FormValues): OnyxCommon.Errors => ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS);

const defaultIncorporationType = reimbursementAccount?.achData?.incorporationType ?? '';

const handleSubmit = useReimbursementAccountStepFormSubmit({
Expand All @@ -48,23 +46,19 @@ function TypeBusiness({reimbursementAccount, onNext, isEditing}: TypeBusinessPro
submitButtonText={translate(isEditing ? 'common.confirm' : 'common.next')}
validate={validate}
onSubmit={handleSubmit}
style={[styles.mh5, styles.flexGrow1]}
submitButtonStyles={[styles.pb5, styles.mb0]}
style={[styles.flexGrow1]}
submitButtonStyles={[styles.p5, styles.mb0]}
>
<Text style={[styles.textHeadline, styles.mb3]}>{translate('businessInfoStep.selectYourCompanysType')}</Text>
<Text style={[styles.textHeadline, styles.mb3, styles.ph5]}>{translate('businessInfoStep.selectYourCompanysType')}</Text>
<InputWrapper
// @ts-expect-error TODO: Remove this once InputWrapper (https://github.com/Expensify/App/issues/31972) is migrated to TypeScript.
InputComponent={Picker}
InputComponent={BusinessTypePicker}
formID={ONYXKEYS.REIMBURSEMENT_ACCOUNT}
inputID={COMPANY_INCORPORATION_TYPE_KEY}
label={translate('businessInfoStep.companyType')}
items={Object.keys(CONST.INCORPORATION_TYPES).map((key) => ({
value: key,
label: translate(`businessInfoStep.incorporationType.${key as IncorporationType}`),
}))}
placeholder={{value: '', label: '-'}}
defaultValue={defaultIncorporationType}
shouldSaveDraft={!isEditing}
wrapperStyle={[styles.ph5, styles.mt4]}
/>
</FormProvider>
);
Expand Down

0 comments on commit f4991ba

Please sign in to comment.