Skip to content

Commit

Permalink
Merge pull request #28313 from MrMuzyk/feat-22878-activate-physical-card
Browse files Browse the repository at this point in the history
activate physical card
  • Loading branch information
grgia authored Oct 10, 2023
2 parents 8c00c5e + cf0700a commit 477c630
Show file tree
Hide file tree
Showing 14 changed files with 343 additions and 9 deletions.
1 change: 1 addition & 0 deletions assets/animations/Magician.json

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ export default {
SETTINGS_ENABLE_PAYMENTS: 'settings/wallet/enable-payments',
SETTINGS_WALLET_TRANSFER_BALANCE: 'settings/wallet/transfer-balance',
SETTINGS_WALLET_CHOOSE_TRANSFER_ACCOUNT: 'settings/wallet/choose-transfer-account',
SETTINGS_WALLET_CARD_ACTIVATE: {
route: 'settings/wallet/cards/:domain/activate',
getRoute: (domain: string) => `settings/wallet/cards/${domain}/activate`,
},
SETTINGS_PERSONAL_DETAILS: 'settings/profile/personal-details',
SETTINGS_PERSONAL_DETAILS_LEGAL_NAME: 'settings/profile/personal-details/legal-name',
SETTINGS_PERSONAL_DETAILS_DATE_OF_BIRTH: 'settings/profile/personal-details/date-of-birth',
Expand Down
16 changes: 13 additions & 3 deletions src/components/HeaderPageLayout.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,26 @@ const propTypes = {
/** Style to apply to the header image container */
// eslint-disable-next-line react/forbid-prop-types
headerContainerStyles: PropTypes.arrayOf(PropTypes.object),

/** Style to apply to the ScrollView container */
// eslint-disable-next-line react/forbid-prop-types
scrollViewContainerStyles: PropTypes.arrayOf(PropTypes.object),

/** Style to apply to the children container */
// eslint-disable-next-line react/forbid-prop-types
childrenContainerStyles: PropTypes.arrayOf(PropTypes.object),
};

const defaultProps = {
backgroundColor: themeColors.appBG,
header: null,
headerContainerStyles: [],
scrollViewContainerStyles: [],
childrenContainerStyles: [],
footer: null,
};

function HeaderPageLayout({backgroundColor, children, footer, headerContainerStyles, style, headerContent, ...propsToPassToHeader}) {
function HeaderPageLayout({backgroundColor, children, footer, headerContainerStyles, scrollViewContainerStyles, childrenContainerStyles, style, headerContent, ...propsToPassToHeader}) {
const {windowHeight, isSmallScreenWidth} = useWindowDimensions();
const {isOffline} = useNetwork();
const appBGColor = StyleUtils.getBackgroundColorStyle(themeColors.appBG);
Expand Down Expand Up @@ -77,14 +87,14 @@ function HeaderPageLayout({backgroundColor, children, footer, headerContainerSty
</View>
)}
<ScrollView
contentContainerStyle={[safeAreaPaddingBottomStyle, style]}
contentContainerStyle={[safeAreaPaddingBottomStyle, style, scrollViewContainerStyles]}
offlineIndicatorStyle={[appBGColor]}
>
{!Browser.isSafari() && <View style={styles.overscrollSpacer(backgroundColor, windowHeight)} />}
<View style={[styles.alignItemsCenter, styles.justifyContentEnd, StyleUtils.getBackgroundColorStyle(backgroundColor), ...headerContainerStyles]}>
{headerContent}
</View>
<View style={[styles.pt5, appBGColor]}>{children}</View>
<View style={[styles.pt5, appBGColor, childrenContainerStyles]}>{children}</View>
</ScrollView>
{!_.isNull(footer) && <FixedFooter>{footer}</FixedFooter>}
</View>
Expand Down
3 changes: 2 additions & 1 deletion src/components/LottieAnimations.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ const ReviewingBankInfo = require('../../assets/animations/ReviewingBankInfo.jso
const WorkspacePlanet = require('../../assets/animations/WorkspacePlanet.json');
const SaveTheWorld = require('../../assets/animations/SaveTheWorld.json');
const Safe = require('../../assets/animations/Safe.json');
const Magician = require('../../assets/animations/Magician.json');

export {ExpensifyLounge, Fireworks, Hands, PreferencesDJ, ReviewingBankInfo, SaveTheWorld, WorkspacePlanet, Safe};
export {ExpensifyLounge, Fireworks, Hands, PreferencesDJ, ReviewingBankInfo, SaveTheWorld, WorkspacePlanet, Safe, Magician};
50 changes: 46 additions & 4 deletions src/components/MagicCodeInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const propTypes = {
errorText: PropTypes.string,

/** Specifies autocomplete hints for the system, so it can provide autofill */
autoComplete: PropTypes.oneOf(['sms-otp', 'one-time-code']).isRequired,
autoComplete: PropTypes.oneOf(['sms-otp', 'one-time-code', 'off']).isRequired,

/* Should submit when the input is complete */
shouldSubmitOnComplete: PropTypes.bool,
Expand All @@ -48,6 +48,12 @@ const propTypes = {

/** Specifies the max length of the input */
maxLength: PropTypes.number,

/** Specifies if the keyboard should be disabled */
isDisableKeyboard: PropTypes.bool,

/** Last pressed digit on BigDigitPad */
lastPressedDigit: PropTypes.string,
};

const defaultProps = {
Expand All @@ -61,6 +67,8 @@ const defaultProps = {
onFulfill: () => {},
hasError: false,
maxLength: CONST.MAGIC_CODE_LENGTH,
isDisableKeyboard: false,
lastPressedDigit: '',
};

/**
Expand Down Expand Up @@ -190,9 +198,21 @@ function MagicCodeInput(props) {
* @param {Object} event
*/
const onKeyPress = ({nativeEvent: {key: keyValue}}) => {
if (keyValue === 'Backspace') {
if (keyValue === 'Backspace' || keyValue === '<') {
let numbers = decomposeString(props.value, props.maxLength);

// If keyboard is disabled and no input is focused we need to remove
// the last entered digit and focus on the correct input
if (props.isDisableKeyboard && focusedIndex === undefined) {
const indexBeforeLastEditIndex = editIndex === 0 ? editIndex : editIndex - 1;

const indexToFocus = numbers[editIndex] === CONST.MAGIC_CODE_EMPTY_CHAR ? indexBeforeLastEditIndex : editIndex;
inputRefs.current[indexToFocus].focus();
props.onChangeText(props.value.substring(0, indexToFocus));

return;
}

// If the currently focused index already has a value, it will delete
// that value but maintain the focus on the same input.
if (numbers[focusedIndex] !== CONST.MAGIC_CODE_EMPTY_CHAR) {
Expand Down Expand Up @@ -237,13 +257,34 @@ function MagicCodeInput(props) {
}
};

/**
* If isDisableKeyboard is true we will have to call onKeyPress and onChangeText manually
* as the press on digit pad will not trigger native events. We take lastPressedDigit from props
* as it stores the last pressed digit pressed on digit pad. We take only the first character
* as anything after that is added to differentiate between two same digits passed in a row.
*/

useEffect(() => {
if (!props.isDisableKeyboard) {
return;
}

const value = props.lastPressedDigit.charAt(0);
onKeyPress({nativeEvent: {key: value}});
onChangeText(value);

// We have not added:
// + the onChangeText and onKeyPress as the dependencies because we only want to run this when lastPressedDigit changes.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.lastPressedDigit, props.isDisableKeyboard]);

return (
<>
<View style={[styles.magicCodeInputContainer]}>
{_.map(getInputPlaceholderSlots(props.maxLength), (index) => (
<View
key={index}
style={[styles.w15]}
style={props.maxLength === CONST.MAGIC_CODE_LENGTH ? [styles.w15] : [styles.flex1, index !== 0 && styles.ml3]}
>
<View
style={[
Expand All @@ -265,8 +306,9 @@ function MagicCodeInput(props) {
ref.setAttribute('type', 'search');
}
}}
disableKeyboard={props.isDisableKeyboard}
autoFocus={index === 0 && props.autoFocus}
inputMode="numeric"
inputMode={props.isDisableKeyboard ? 'none' : 'numeric'}
textContentType="oneTimeCode"
name={props.name}
maxLength={props.maxLength}
Expand Down
8 changes: 8 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,14 @@ export default {
copyCardNumber: 'Copy card number',
},
},
activateCardPage: {
activateCard: 'Activate card',
pleaseEnterLastFour: 'Please enter the last four digits of your card.',
activatePhysicalCard: 'Activate physical card',
error: {
thatDidntMatch: "That didn't match the last 4 digits on your card. Please try again.",
},
},
transferAmountPage: {
transfer: ({amount}: TransferParams) => `Transfer${amount ? ` ${amount}` : ''}`,
instant: 'Instant (Debit card)',
Expand Down
8 changes: 8 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,14 @@ export default {
copyCardNumber: 'Copiar número de la tarjeta',
},
},
activateCardPage: {
activateCard: 'Activar tarjeta',
pleaseEnterLastFour: 'Introduce los cuatro últimos dígitos de la tarjeta.',
activatePhysicalCard: 'Activar tarjeta física',
error: {
thatDidntMatch: 'Los 4 últimos dígitos de tu tarjeta no coinciden. Por favor, inténtalo de nuevo.',
},
},
transferAmountPage: {
transfer: ({amount}: TransferParams) => `Transferir${amount ? ` ${amount}` : ''}`,
instant: 'Instante',
Expand Down
1 change: 1 addition & 0 deletions src/libs/Navigation/AppNavigator/ModalStackNavigators.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ const SettingsModalStackNavigator = createModalStackNavigator({
Settings_Lounge_Access: () => require('../../../pages/settings/Profile/LoungeAccessPage').default,
Settings_Wallet: () => require('../../../pages/settings/Wallet/WalletPage').default,
Settings_Wallet_DomainCards: () => require('../../../pages/settings/Wallet/ExpensifyCardPage').default,
Settings_Wallet_Card_Activate: () => require('../../../pages/settings/Wallet/ActivatePhysicalCardPage').default,
Settings_Wallet_Transfer_Balance: () => require('../../../pages/settings/Wallet/TransferBalancePage').default,
Settings_Wallet_Choose_Transfer_Account: () => require('../../../pages/settings/Wallet/ChooseTransferAccountPage').default,
Settings_Wallet_EnablePayments: () => require('../../../pages/EnablePayments/EnablePaymentsPage').default,
Expand Down
4 changes: 4 additions & 0 deletions src/libs/Navigation/linkingConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ export default {
path: ROUTES.SETTINGS_WALLET_CHOOSE_TRANSFER_ACCOUNT,
exact: true,
},
Settings_Wallet_Card_Activate: {
path: ROUTES.SETTINGS_WALLET_CARD_ACTIVATE.route,
exact: true,
},
Settings_Add_Debit_Card: {
path: ROUTES.SETTINGS_ADD_DEBIT_CARD,
exact: true,
Expand Down
63 changes: 63 additions & 0 deletions src/libs/actions/Card.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import Onyx from 'react-native-onyx';
import ONYXKEYS from '../../ONYXKEYS';
import * as API from '../API';

/**
* Activates the physical Expensify card based on the last four digits of the card number
*
* @param {Number} lastFourDigits
* @param {Number} cardID
*/
function activatePhysicalExpensifyCard(lastFourDigits, cardID) {
API.write(
'ActivatePhysicalExpensifyCard',
{lastFourDigits, cardID},
{
optimisticData: [
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.CARD_LIST,
value: {
[cardID]: {
errors: null,
isLoading: true,
},
},
},
],
successData: [
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.CARD_LIST,
value: {
[cardID]: {
isLoading: false,
},
},
},
],
failureData: [
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.CARD_LIST,
value: {
[cardID]: {
isLoading: false,
},
},
},
],
},
);
}

/**
* Clears errors for a specific cardID
*
* @param {Number} cardID
*/
function clearCardListErrors(cardID) {
Onyx.merge(ONYXKEYS.CARD_LIST, {[cardID]: {errors: null, isLoading: false}});
}

export {activatePhysicalExpensifyCard, clearCardListErrors};
Loading

0 comments on commit 477c630

Please sign in to comment.