Skip to content

Commit

Permalink
Merge pull request #34716 from tienifr/ts-migration/32003
Browse files Browse the repository at this point in the history
[TS migration] Migrate `SettingsWallet` page to TypeScript
  • Loading branch information
Julesssss authored Feb 26, 2024
2 parents fa08e17 + b8caf36 commit 9bfe961
Show file tree
Hide file tree
Showing 19 changed files with 307 additions and 440 deletions.
2 changes: 1 addition & 1 deletion src/components/AddressSearch/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ type AddressSearchProps = {
predefinedPlaces?: Place[] | null;

/** A map of inputID key names */
renamedInputKeys: RenamedInputKeysProps;
renamedInputKeys?: RenamedInputKeysProps;

/** Maximum number of characters allowed in search input */
maxInputLength?: number;
Expand Down
2 changes: 1 addition & 1 deletion src/languages/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ type EnterMagicCodeParams = {contactMethod: string};

type TransferParams = {amount: string};

type InstantSummaryParams = {rate: number; minAmount: number};
type InstantSummaryParams = {rate: string; minAmount: string};

type NotYouParams = {user: string};

Expand Down
18 changes: 7 additions & 11 deletions src/libs/GetPhysicalCardUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,24 +54,20 @@ function setCurrentRoute(currentRoute: string, domain: string, privatePersonalDe
* @param privatePersonalDetails
* @returns
*/
function getUpdatedDraftValues(
draftValues: OnyxEntry<GetPhysicalCardForm>,
privatePersonalDetails: OnyxEntry<PrivatePersonalDetails>,
loginList: OnyxEntry<LoginList>,
): Partial<GetPhysicalCardForm> {
function getUpdatedDraftValues(draftValues: OnyxEntry<GetPhysicalCardForm>, privatePersonalDetails: OnyxEntry<PrivatePersonalDetails>, loginList: OnyxEntry<LoginList>): GetPhysicalCardForm {
const {address, legalFirstName, legalLastName, phoneNumber} = privatePersonalDetails ?? {};

return {
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
// we do not need to use nullish coalescing here because we want to allow empty strings
legalFirstName: draftValues?.legalFirstName || legalFirstName,
legalLastName: draftValues?.legalLastName || legalLastName,
addressLine1: draftValues?.addressLine1 || address?.street.split('\n')[0],
legalFirstName: draftValues?.legalFirstName || legalFirstName || '',
legalLastName: draftValues?.legalLastName || legalLastName || '',
addressLine1: draftValues?.addressLine1 || address?.street.split('\n')[0] || '',
addressLine2: draftValues?.addressLine2 || address?.street.split('\n')[1] || '',
city: draftValues?.city || address?.city,
country: draftValues?.country || address?.country,
city: draftValues?.city || address?.city || '',
country: draftValues?.country || address?.country || '',
phoneNumber: draftValues?.phoneNumber || phoneNumber || UserUtils.getSecondaryPhoneLogin(loginList) || '',
state: draftValues?.state || address?.state,
state: draftValues?.state || address?.state || '',
zipPostCode: draftValues?.zipPostCode || address?.zip || '',
/* eslint-enable @typescript-eslint/prefer-nullish-coalescing */
};
Expand Down
1 change: 1 addition & 0 deletions src/libs/Navigation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,7 @@ type PublicScreensParamList = {
shortLivedAuthToken?: string;
shortLivedToken?: string;
exitTo?: Routes | HybridAppRoute;
domain?: Routes;
};
[SCREENS.VALIDATE_LOGIN]: {
accountID: string;
Expand Down
1 change: 1 addition & 0 deletions src/libs/actions/Card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,3 +178,4 @@ function revealVirtualCardDetails(cardID: number): Promise<Response> {
}

export {requestReplacementExpensifyCard, activatePhysicalExpensifyCard, clearCardListErrors, reportVirtualExpensifyCardFraud, revealVirtualCardDetails};
export type {ReplacementReason};
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import lodashGet from 'lodash/get';
import PropTypes from 'prop-types';
import type {StackScreenProps} from '@react-navigation/stack';
import React, {useCallback, useEffect, useRef, useState} from 'react';
import {View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
import BigNumberPad from '@components/BigNumberPad';
import Button from '@components/Button';
import IllustratedHeaderPageLayout from '@components/IllustratedHeaderPageLayout';
import LottieAnimations from '@components/LottieAnimations';
import MagicCodeInput from '@components/MagicCodeInput';
import type {MagicCodeInputHandle} from '@components/MagicCodeInput';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
Expand All @@ -19,42 +19,32 @@ import * as CardUtils from '@libs/CardUtils';
import * as DeviceCapabilities from '@libs/DeviceCapabilities';
import * as ErrorUtils from '@libs/ErrorUtils';
import Navigation from '@libs/Navigation/Navigation';
import type {PublicScreensParamList} from '@libs/Navigation/types';
import NotFoundPage from '@pages/ErrorPage/NotFoundPage';
import * as CardSettings from '@userActions/Card';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import SCREENS from '@src/SCREENS';
import assignedCardPropTypes from './assignedCardPropTypes';
import type {Card} from '@src/types/onyx';
import {isEmptyObject} from '@src/types/utils/EmptyObject';

const propTypes = {
/* Onyx Props */

/** The details about the Expensify cards */
cardList: PropTypes.objectOf(assignedCardPropTypes),

/** Navigation route context info provided by react navigation */
route: PropTypes.shape({
params: PropTypes.shape({
/** domain passed via route /settings/wallet/card/:domain */
domain: PropTypes.string,
}),
}).isRequired,
type ActivatePhysicalCardPageOnyxProps = {
/** Card list propTypes */
cardList: OnyxEntry<Record<string, Card>>;
};

const defaultProps = {
cardList: {},
};
type ActivatePhysicalCardPageProps = ActivatePhysicalCardPageOnyxProps & StackScreenProps<PublicScreensParamList, typeof SCREENS.TRANSITION_BETWEEN_APPS>;

const LAST_FOUR_DIGITS_LENGTH = 4;
const MAGIC_INPUT_MIN_HEIGHT = 86;

function ActivatePhysicalCardPage({
cardList,
route: {
params: {domain},
params: {domain = ''},
},
}) {
}: ActivatePhysicalCardPageProps) {
const theme = useTheme();
const styles = useThemeStyles();
const {isExtraSmallScreenHeight} = useWindowDimensions();
Expand All @@ -65,23 +55,24 @@ function ActivatePhysicalCardPage({
const [lastFourDigits, setLastFourDigits] = useState('');
const [lastPressedDigit, setLastPressedDigit] = useState('');

const domainCards = CardUtils.getDomainCards(cardList)[domain];
const physicalCard = _.find(domainCards, (card) => !card.isVirtual) || {};
const cardID = lodashGet(physicalCard, 'cardID', 0);
const cardError = ErrorUtils.getLatestErrorMessage(physicalCard);
const domainCards = CardUtils.getDomainCards(cardList)[domain] ?? [];
const physicalCard = domainCards.find((card) => !card.isVirtual);
const cardID = physicalCard?.cardID ?? 0;
const cardError = ErrorUtils.getLatestErrorMessage(physicalCard ?? {});

const activateCardCodeInputRef = useRef(null);
const activateCardCodeInputRef = useRef<MagicCodeInputHandle>(null);

/**
* If state of the card is CONST.EXPENSIFY_CARD.STATE.OPEN, navigate to card details screen.
*/
useEffect(() => {
if (physicalCard.isLoading || lodashGet(cardList, `${cardID}.state`, 0) !== CONST.EXPENSIFY_CARD.STATE.OPEN) {
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
if (physicalCard?.isLoading || cardList?.[cardID]?.state !== CONST.EXPENSIFY_CARD.STATE.OPEN) {
return;
}

Navigation.navigate(ROUTES.SETTINGS_WALLET_DOMAINCARD.getRoute(domain));
}, [cardID, cardList, domain, physicalCard.isLoading]);
}, [cardID, cardList, domain, physicalCard?.isLoading]);

useEffect(
() => () => {
Expand All @@ -95,17 +86,13 @@ function ActivatePhysicalCardPage({
*
* NOTE: If the same digit is pressed twice in a row, append it to the end of the string
* so that useEffect inside MagicCodeInput will be triggered by artificial change of the value.
*
* @param {String} key
*/
const updateLastPressedDigit = useCallback((key) => setLastPressedDigit(lastPressedDigit === key ? lastPressedDigit + key : key), [lastPressedDigit]);
const updateLastPressedDigit = useCallback((key: string) => setLastPressedDigit(lastPressedDigit === key ? lastPressedDigit + key : key), [lastPressedDigit]);

/**
* Handle card activation code input
*
* @param {String} text
*/
const onCodeInput = (text) => {
const onCodeInput = (text: string) => {
setFormError('');

if (cardError) {
Expand All @@ -116,7 +103,7 @@ function ActivatePhysicalCardPage({
};

const submitAndNavigateToNextPage = useCallback(() => {
activateCardCodeInputRef.current.blur();
activateCardCodeInputRef.current?.blur();

if (lastFourDigits.replace(CONST.MAGIC_CODE_EMPTY_CHAR, '').length !== LAST_FOUR_DIGITS_LENGTH) {
setFormError('activateCardPage.error.thatDidntMatch');
Expand All @@ -126,7 +113,7 @@ function ActivatePhysicalCardPage({
CardSettings.activatePhysicalExpensifyCard(lastFourDigits, cardID);
}, [lastFourDigits, cardID]);

if (_.isEmpty(physicalCard)) {
if (isEmptyObject(physicalCard)) {
return <NotFoundPage />;
}

Expand Down Expand Up @@ -161,7 +148,7 @@ function ActivatePhysicalCardPage({
<Button
success
isDisabled={isOffline}
isLoading={physicalCard.isLoading}
isLoading={physicalCard?.isLoading}
medium={isExtraSmallScreenHeight}
style={[styles.w100, styles.p5, styles.mtAuto]}
onPress={submitAndNavigateToNextPage}
Expand All @@ -172,11 +159,9 @@ function ActivatePhysicalCardPage({
);
}

ActivatePhysicalCardPage.propTypes = propTypes;
ActivatePhysicalCardPage.defaultProps = defaultProps;
ActivatePhysicalCardPage.displayName = 'ActivatePhysicalCardPage';

export default withOnyx({
export default withOnyx<ActivatePhysicalCardPageProps, ActivatePhysicalCardPageOnyxProps>({
cardList: {
key: ONYXKEYS.CARD_LIST,
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import PropTypes from 'prop-types';
import React, {useEffect, useRef} from 'react';
import {View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
import AddressSearch from '@components/AddressSearch';
import CheckboxWithLabel from '@components/CheckboxWithLabel';
import FormProvider from '@components/Form/FormProvider';
import InputWrapper from '@components/Form/InputWrapper';
import type {FormInputErrors, FormOnyxValues} from '@components/Form/types';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import type {AnimatedTextInputRef} from '@components/RNTextInput';
import ScreenWrapper from '@components/ScreenWrapper';
import StatePicker from '@components/StatePicker';
import Text from '@components/Text';
Expand All @@ -20,26 +22,31 @@ import * as ValidationUtils from '@libs/ValidationUtils';
import * as PaymentMethods from '@userActions/PaymentMethods';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {AddDebitCardForm} from '@src/types/form';
import INPUT_IDS from '@src/types/form/AddDebitCardForm';

const propTypes = {
/* Onyx Props */
formData: PropTypes.shape({
setupComplete: PropTypes.bool,
}),
type DebitCardPageOnyxProps = {
/** Form data propTypes */
formData: OnyxEntry<AddDebitCardForm>;
};

const defaultProps = {
formData: {
setupComplete: false,
},
};
type DebitCardPageProps = DebitCardPageOnyxProps;

const REQUIRED_FIELDS = [
INPUT_IDS.NAME_ON_CARD,
INPUT_IDS.CARD_NUMBER,
INPUT_IDS.EXPIRATION_DATE,
INPUT_IDS.SECURITY_CODE,
INPUT_IDS.ADDRESS_STREET,
INPUT_IDS.ADDRESS_ZIP_CODE,
INPUT_IDS.ADDRESS_STATE,
];

function DebitCardPage(props) {
function DebitCardPage({formData}: DebitCardPageProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const prevFormDataSetupComplete = usePrevious(props.formData.setupComplete);
const nameOnCardRef = useRef(null);
const prevFormDataSetupComplete = usePrevious(!!formData?.setupComplete);
const nameOnCardRef = useRef<AnimatedTextInputRef>(null);

/**
* Reset the form values on the mount and unmount so that old errors don't show when this form is displayed again.
Expand All @@ -53,20 +60,18 @@ function DebitCardPage(props) {
}, []);

useEffect(() => {
if (prevFormDataSetupComplete || !props.formData.setupComplete) {
if (prevFormDataSetupComplete || !formData?.setupComplete) {
return;
}

PaymentMethods.continueSetup();
}, [prevFormDataSetupComplete, props.formData.setupComplete]);
}, [prevFormDataSetupComplete, formData?.setupComplete]);

/**
* @param {Object} values - form input values passed by the Form component
* @returns {Boolean}
* @param values - form input values passed by the Form component
*/
const validate = (values) => {
const requiredFields = ['nameOnCard', 'cardNumber', 'expirationDate', 'securityCode', 'addressStreet', 'addressZipCode', 'addressState'];
const errors = ValidationUtils.getFieldRequiredErrors(values, requiredFields);
const validate = (values: FormOnyxValues<typeof ONYXKEYS.FORMS.ADD_DEBIT_CARD_FORM>): FormInputErrors<typeof ONYXKEYS.FORMS.ADD_DEBIT_CARD_FORM> => {
const errors = ValidationUtils.getFieldRequiredErrors(values, REQUIRED_FIELDS);

if (values.nameOnCard && !ValidationUtils.isValidLegalName(values.nameOnCard)) {
errors.nameOnCard = 'addDebitCardPage.error.invalidName';
Expand Down Expand Up @@ -101,7 +106,7 @@ function DebitCardPage(props) {

return (
<ScreenWrapper
onEntryTransitionEnd={() => nameOnCardRef.current && nameOnCardRef.current.focus()}
onEntryTransitionEnd={() => nameOnCardRef.current?.focus()}
includeSafeAreaPaddingBottom={false}
testID={DebitCardPage.displayName}
>
Expand Down Expand Up @@ -206,11 +211,9 @@ function DebitCardPage(props) {
);
}

DebitCardPage.propTypes = propTypes;
DebitCardPage.defaultProps = defaultProps;
DebitCardPage.displayName = 'DebitCardPage';

export default withOnyx({
export default withOnyx<DebitCardPageProps, DebitCardPageOnyxProps>({
formData: {
key: ONYXKEYS.FORMS.ADD_DEBIT_CARD_FORM,
},
Expand Down
Loading

0 comments on commit 9bfe961

Please sign in to comment.