Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(expensify-card): add get physical card button and routes #28453

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
a147db2
feat(expensify-card): add get physical card button and routes
pac-guerreiro Sep 29, 2023
ed99be3
style: apply prettier
pac-guerreiro Oct 12, 2023
2f21fbb
refactor(get physical card): rename translation keys to match onyx pr…
pac-guerreiro Oct 17, 2023
0f29db9
feat(get physical card): add missing spanish translations
pac-guerreiro Oct 17, 2023
2096411
refactor(get physical card): reuse UI from personal details address page
pac-guerreiro Oct 17, 2023
88e280b
fix(get physical card): wrong params passed to Navigation.goToNextPhy…
pac-guerreiro Oct 17, 2023
5c382ee
fix(get physical card): missing testID error on console
pac-guerreiro Oct 17, 2023
3993850
fix(get physical card): wrong data accessed during flow
pac-guerreiro Oct 17, 2023
3ea88a7
fix(wallet): wrong naming for domain card route
pac-guerreiro Oct 18, 2023
014f0bc
fix(get physical card): top margin on address page step
pac-guerreiro Oct 18, 2023
6530e75
fix(get physical card): wrong prop type supplied to children
pac-guerreiro Oct 18, 2023
4ff162d
refactor(get physical card): rename routes
pac-guerreiro Oct 18, 2023
53afcf3
fix(get physical card): wrong data sent through final API request
pac-guerreiro Oct 18, 2023
be37b21
refactor(get physical card): replace manual solution with existing util
pac-guerreiro Oct 18, 2023
d80f3d5
fix(get physical card): wrong layout on confirm page step
pac-guerreiro Oct 18, 2023
63a4de9
feat(get physical card): complete Spanish translation
pac-guerreiro Oct 19, 2023
28a309c
refactor: code style
pac-guerreiro Oct 23, 2023
53bfa83
feat(get physical card): use draft data for temporary modifications a…
pac-guerreiro Oct 23, 2023
5f5f158
refactor(get physical card): phone number validation logic
pac-guerreiro Oct 23, 2023
c69825c
fix(get physical card): wrong address displayed on confirmation page
pac-guerreiro Oct 24, 2023
2d61f54
chore(get physical card): add expensify card mock for testing purposes
pac-guerreiro Oct 24, 2023
8bbf4ab
fix(get physical card): formatted address showing undefined
pac-guerreiro Oct 24, 2023
6e28feb
fix(get physical card): wrong condition to render get physical card b…
pac-guerreiro Oct 24, 2023
d6b9d52
fix(get physical card): wrong mock data
pac-guerreiro Oct 24, 2023
d3772fe
fix(get physical card): wrong validation of address while getting nex…
pac-guerreiro Oct 25, 2023
f51068d
fix(get physical card): validate callback not returning an object cau…
pac-guerreiro Oct 25, 2023
1290c7c
fix(TextInput): container styles not applied to text input help message
pac-guerreiro Oct 26, 2023
456c7c9
fix(get physical card): broken phone number validation
pac-guerreiro Oct 26, 2023
f777b38
chore(get physical card): apply prettier
pac-guerreiro Oct 31, 2023
a9559f4
refactor(get physical card): separate utils from core navigation
pac-guerreiro Nov 6, 2023
d6b86f7
fix(get physical card): RHP getting closed on back button pressed
pac-guerreiro Nov 6, 2023
db37d3b
fix(get physical card): being able to access a step before completing…
pac-guerreiro Nov 6, 2023
67449d7
fix(get physical card): navigating from confirmation page not pushing…
pac-guerreiro Nov 6, 2023
d2576bb
fix(get physical card): user getting redirected to confirm page when …
pac-guerreiro Nov 6, 2023
2d09ef7
fix(get physical card): redirect not replacing current screen on navi…
pac-guerreiro Nov 6, 2023
bbaea03
fix(get physical card): draft values initialization
pac-guerreiro Nov 6, 2023
16f8494
fix: invalid types on setDraftValues
pac-guerreiro Nov 8, 2023
26f0ac7
refactor: migrate FormActions to Typescript
pac-guerreiro Nov 8, 2023
314c7b7
fix: add missing getPhysicalCardFormDraft
pac-guerreiro Nov 8, 2023
a9f18aa
fix(get physical card): add missing onyx value types
pac-guerreiro Nov 8, 2023
8d11d57
chore(get physical card): removed mock data
pac-guerreiro Nov 13, 2023
40acd98
doc(get physical card): add better comments to address fields
pac-guerreiro Nov 13, 2023
218f194
chore(get physical card): remove unused import
pac-guerreiro Nov 13, 2023
6fe8995
Merge branch 'main' into feature/add-get-physical-card-button-and-nec…
pac-guerreiro Nov 15, 2023
47ad65e
chore: remove unused file
pac-guerreiro Nov 15, 2023
3172f9a
refactor: use form utils to get draft key
pac-guerreiro Nov 15, 2023
8bb38f4
feat(get physical card): enable physical card request to API
pac-guerreiro Nov 16, 2023
e600b84
Merge branch 'main' into feature/add-get-physical-card-button-and-nec…
pac-guerreiro Nov 16, 2023
f213505
fix(get physical card): being able to access the flow with the draft …
pac-guerreiro Nov 18, 2023
41b7976
fix(get physical card): being able to access the flow when the domain…
pac-guerreiro Nov 18, 2023
e831853
fix(get physical card): ability to access the flow when there is no p…
pac-guerreiro Nov 18, 2023
57cf5e5
fix(expensify card): pressing go back button on not found state close…
pac-guerreiro Nov 18, 2023
d0ea79a
fix(get physical card): wrong accessibility prop names
pac-guerreiro Nov 18, 2023
1054b16
Merge branch 'main' into feature/add-get-physical-card-button-and-nec…
pac-guerreiro Nov 18, 2023
56554f6
style: apply linter
pac-guerreiro Nov 18, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,8 @@ const ONYXKEYS = {
REPORT_PHYSICAL_CARD_FORM_DRAFT: 'requestPhysicalCardFormDraft',
REPORT_VIRTUAL_CARD_FRAUD: 'reportVirtualCardFraudForm',
REPORT_VIRTUAL_CARD_FRAUD_DRAFT: 'reportVirtualCardFraudFormDraft',
GET_PHYSICAL_CARD_FORM: 'getPhysicalCardForm',
GET_PHYSICAL_CARD_FORM_DRAFT: 'getPhysicalCardFormDraft',
},
} as const;

Expand Down Expand Up @@ -500,6 +502,8 @@ type OnyxValues = {
[ONYXKEYS.FORMS.REPORT_VIRTUAL_CARD_FRAUD_DRAFT]: OnyxTypes.Form;
[ONYXKEYS.FORMS.REPORT_PHYSICAL_CARD_FORM]: OnyxTypes.Form;
[ONYXKEYS.FORMS.REPORT_PHYSICAL_CARD_FORM_DRAFT]: OnyxTypes.Form;
[ONYXKEYS.FORMS.GET_PHYSICAL_CARD_FORM]: OnyxTypes.Form;
[ONYXKEYS.FORMS.GET_PHYSICAL_CARD_FORM_DRAFT]: OnyxTypes.Form | undefined;
};

type OnyxKeyValue<TOnyxKey extends (OnyxKey | OnyxCollectionKey) & keyof OnyxValues> = OnyxEntry<OnyxValues[TOnyxKey]>;
Expand Down
16 changes: 16 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,22 @@ export default {
route: '/settings/wallet/card/:domain/report-virtual-fraud',
getRoute: (domain: string) => `/settings/wallet/card/${domain}/report-virtual-fraud`,
},
SETTINGS_WALLET_CARD_GET_PHYSICAL_NAME: {
route: '/settings/wallet/card/:domain/get-physical/name',
getRoute: (domain: string) => `/settings/wallet/card/${domain}/get-physical/name`,
},
SETTINGS_WALLET_CARD_GET_PHYSICAL_PHONE: {
route: '/settings/wallet/card/:domain/get-physical/phone',
getRoute: (domain: string) => `/settings/wallet/card/${domain}/get-physical/phone`,
},
SETTINGS_WALLET_CARD_GET_PHYSICAL_ADDRESS: {
route: '/settings/wallet/card/:domain/get-physical/address',
getRoute: (domain: string) => `/settings/wallet/card/${domain}/get-physical/address`,
},
SETTINGS_WALLET_CARD_GET_PHYSICAL_CONFIRM: {
route: '/settings/wallet/card/:domain/get-physical/confirm',
getRoute: (domain: string) => `/settings/wallet/card/${domain}/get-physical/confirm`,
},
SETTINGS_ADD_DEBIT_CARD: 'settings/wallet/add-debit-card',
SETTINGS_ADD_BANK_ACCOUNT: 'settings/wallet/add-bank-account',
SETTINGS_ENABLE_PAYMENTS: 'settings/wallet/enable-payments',
Expand Down
8 changes: 7 additions & 1 deletion src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@ export default {
SECURITY: 'Settings_Security',
STATUS: 'Settings_Status',
WALLET: 'Settings_Wallet',
WALLET_DOMAIN_CARDS: 'Settings_Wallet_DomainCards',
WALLET_DOMAIN_CARD: 'Settings_Wallet_DomainCard',
WALLET_CARD_GET_PHYSICAL: {
NAME: 'Settings_Card_Get_Physical_Name',
PHONE: 'Settings_Card_Get_Physical_Phone',
ADDRESS: 'Settings_Card_Get_Physical_Address',
CONFIRM: 'Settings_Card_Get_Physical_Confirm',
},
},
SAVE_THE_WORLD: {
ROOT: 'SaveTheWorld_Root',
Expand Down
223 changes: 223 additions & 0 deletions src/components/AddressForm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST';
import lodashGet from 'lodash/get';
import PropTypes from 'prop-types';
import React, {useCallback} from 'react';
import {View} from 'react-native';
import _ from 'underscore';
import useLocalize from '@hooks/useLocalize';
import Navigation from '@libs/Navigation/Navigation';
import * as ValidationUtils from '@libs/ValidationUtils';
import styles from '@styles/styles';
import CONST from '@src/CONST';
import AddressSearch from './AddressSearch';
import CountrySelector from './CountrySelector';
import Form from './Form';
import StatePicker from './StatePicker';
import TextInput from './TextInput';

const propTypes = {
/** Address city field */
city: PropTypes.string,

/** Address country field */
country: PropTypes.string,

/** Address state field */
state: PropTypes.string,

/** Address street line 1 field */
street1: PropTypes.string,

/** Address street line 2 field */
street2: PropTypes.string,

/** Address zip code field */
zip: PropTypes.string,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These please all need some comments above them like we have in other parts of the app.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pac-guerreiro could you address this comment


/** Callback which is executed when the user changes address, city or state */
onAddressChanged: PropTypes.func,

/** Callback which is executed when the user submits his address changes */
onSubmit: PropTypes.func.isRequired,

/** Whether or not should the form data should be saved as draft */
shouldSaveDraft: PropTypes.bool,

/** Text displayed on the bottom submit button */
submitButtonText: PropTypes.string,

/** A unique Onyx key identifying the form */
formID: PropTypes.string.isRequired,
};

const defaultProps = {
city: '',
country: '',
onAddressChanged: () => {},
shouldSaveDraft: false,
state: '',
street1: '',
street2: '',
submitButtonText: '',
zip: '',
};

function AddressForm({city, country, formID, onAddressChanged, onSubmit, shouldSaveDraft, state, street1, street2, submitButtonText, zip}) {
const {translate} = useLocalize();
const zipSampleFormat = lodashGet(CONST.COUNTRY_ZIP_REGEX_DATA, [country, 'samples'], '');
const zipFormat = translate('common.zipCodeExampleFormat', {zipSampleFormat});
const isUSAForm = country === CONST.COUNTRY.US;

/**
* @param {Function} translate - translate function
* @param {Boolean} isUSAForm - selected country ISO code is US
* @param {Object} values - form input values
* @returns {Object} - An object containing the errors for each inputID
*/
const validator = useCallback((values) => {
const errors = {};
const requiredFields = ['addressLine1', 'city', 'country', 'state'];

// Check "State" dropdown is a valid state if selected Country is USA
if (values.country === CONST.COUNTRY.US && !COMMON_CONST.STATES[values.state]) {
errors.state = 'common.error.fieldRequired';
}

// Add "Field required" errors if any required field is empty
_.each(requiredFields, (fieldKey) => {
if (ValidationUtils.isRequiredFulfilled(values[fieldKey])) {
return;
}
errors[fieldKey] = 'common.error.fieldRequired';
});

// If no country is selected, default value is an empty string and there's no related regex data so we default to an empty object
const countryRegexDetails = lodashGet(CONST.COUNTRY_ZIP_REGEX_DATA, values.country, {});

// The postal code system might not exist for a country, so no regex either for them.
const countrySpecificZipRegex = lodashGet(countryRegexDetails, 'regex');
const countryZipFormat = lodashGet(countryRegexDetails, 'samples');

if (countrySpecificZipRegex) {
if (!countrySpecificZipRegex.test(values.zipPostCode.trim().toUpperCase())) {
if (ValidationUtils.isRequiredFulfilled(values.zipPostCode.trim())) {
errors.zipPostCode = ['privatePersonalDetails.error.incorrectZipFormat', {zipFormat: countryZipFormat}];
} else {
errors.zipPostCode = 'common.error.fieldRequired';
}
}
} else if (!CONST.GENERIC_ZIP_CODE_REGEX.test(values.zipPostCode.trim().toUpperCase())) {
errors.zipPostCode = 'privatePersonalDetails.error.incorrectZipFormat';
}

return errors;
}, []);

return (
<Form
style={[styles.flexGrow1, styles.mh5]}
formID={formID}
validate={validator}
onSubmit={onSubmit}
submitButtonText={submitButtonText}
enabledWhenOffline
>
<View style={styles.formSpaceVertical} />
<View>
<AddressSearch
inputID="addressLine1"
label={translate('common.addressLine', {lineNumber: 1})}
onValueChange={(data, key) => {
onAddressChanged(data, key);
// This enforces the country selector to use the country from address instead of the country from URL
Navigation.setParams({country: undefined});
}}
defaultValue={street1 || ''}
renamedInputKeys={{
street: 'addressLine1',
street2: 'addressLine2',
city: 'city',
state: 'state',
zipCode: 'zipPostCode',
country: 'country',
}}
maxInputLength={CONST.FORM_CHARACTER_LIMIT}
shouldSaveDraft={shouldSaveDraft}
/>
</View>
<View style={styles.formSpaceVertical} />
<TextInput
inputID="addressLine2"
label={translate('common.addressLine', {lineNumber: 2})}
aria-label={translate('common.addressLine', {lineNumber: 2})}
role={CONST.ACCESSIBILITY_ROLE.TEXT}
defaultValue={street2 || ''}
maxLength={CONST.FORM_CHARACTER_LIMIT}
spellCheck={false}
shouldSaveDraft={shouldSaveDraft}
/>
<View style={styles.formSpaceVertical} />
<View style={styles.mhn5}>
<CountrySelector
inputID="country"
value={country}
shouldSaveDraft={shouldSaveDraft}
/>
</View>
<View style={styles.formSpaceVertical} />
{isUSAForm ? (
<View style={styles.mhn5}>
<StatePicker
inputID="state"
defaultValue={state}
onValueChange={onAddressChanged}
shouldSaveDraft={shouldSaveDraft}
/>
</View>
) : (
<TextInput
inputID="state"
label={translate('common.stateOrProvince')}
aria-label={translate('common.stateOrProvince')}
role={CONST.ACCESSIBILITY_ROLE.TEXT}
value={state || ''}
maxLength={CONST.FORM_CHARACTER_LIMIT}
spellCheck={false}
onValueChange={onAddressChanged}
shouldSaveDraft={shouldSaveDraft}
/>
)}
<View style={styles.formSpaceVertical} />
<TextInput
inputID="city"
label={translate('common.city')}
aria-label={translate('common.city')}
role={CONST.ACCESSIBILITY_ROLE.TEXT}
defaultValue={city || ''}
maxLength={CONST.FORM_CHARACTER_LIMIT}
spellCheck={false}
onValueChange={onAddressChanged}
shouldSaveDraft={shouldSaveDraft}
/>
<View style={styles.formSpaceVertical} />
<TextInput
inputID="zipPostCode"
label={translate('common.zipPostCode')}
aria-label={translate('common.zipPostCode')}
role={CONST.ACCESSIBILITY_ROLE.TEXT}
autoCapitalize="characters"
defaultValue={zip || ''}
maxLength={CONST.BANK_ACCOUNT.MAX_LENGTH.ZIP_CODE}
hint={zipFormat}
onValueChange={onAddressChanged}
shouldSaveDraft={shouldSaveDraft}
/>
</Form>
);
}

AddressForm.defaultProps = defaultProps;
AddressForm.displayName = 'AddressForm';
AddressForm.propTypes = propTypes;

export default AddressForm;
6 changes: 4 additions & 2 deletions src/components/Form.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
import compose from '@libs/compose';
import * as ErrorUtils from '@libs/ErrorUtils';
import FormUtils from '@libs/FormUtils';
import * as ValidationUtils from '@libs/ValidationUtils';
import Visibility from '@libs/Visibility';
import stylePropTypes from '@styles/stylePropTypes';
Expand Down Expand Up @@ -303,7 +304,8 @@ function Form(props) {

// We want to initialize the input value if it's undefined
if (_.isUndefined(inputValues[inputID])) {
inputValues[inputID] = _.isBoolean(defaultValue) ? defaultValue : defaultValue || '';
// eslint-disable-next-line es/no-nullish-coalescing-operators
inputValues[inputID] = defaultValue ?? '';
Comment on lines +307 to +308
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this being changed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made this change because it's more simpler and does the same thing as before.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that's correct. For example, previously, if defaultValue was false, inputValues[inputID] would be false as well. With your changes, it would be ''.

Copy link
Contributor

@grgia grgia Nov 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pac-guerreiro @allroundexperts Shall we revert this line to avoid breaking other forms?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

although @allroundexperts is this the case you were referring to?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, actually, its the same. My bad. False alarm 🔕

}

// We force the form to set the input value from the defaultValue props if there is a saved valid value
Expand Down Expand Up @@ -553,7 +555,7 @@ export default compose(
key: (props) => props.formID,
},
draftValues: {
key: (props) => `${props.formID}Draft`,
key: (props) => FormUtils.getDraftKey(props.formID),
},
}),
)(Form);
3 changes: 1 addition & 2 deletions src/components/TextInput/BaseTextInput/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ function BaseTextInput(props) {
return (
<>
<View
style={styles.pointerEventsNone}
style={[styles.pointerEventsNone, ...props.containerStyles]}
Copy link
Contributor

@Pujan92 Pujan92 May 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved containerStyles to main View wrapper caused an issue #40519, bcoz containerStyles has max height restriction which is specifically required only for TextInput. Due to this change the error message overflows and overlaps with the bottom content.

// eslint-disable-next-line react/jsx-props-no-spreading
{...(props.shouldInterceptSwipe && SwipeInterceptPanResponder.panHandlers)}
>
Expand All @@ -261,7 +261,6 @@ function BaseTextInput(props) {
style={[
props.autoGrowHeight && styles.autoGrowHeightInputContainer(textInputHeight, variables.componentSizeLarge, maxHeight),
!isMultiline && styles.componentHeightLarge,
...props.containerStyles,
]}
>
<View
Expand Down
22 changes: 22 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,7 @@ export default {
availableSpend: 'Remaining limit',
virtualCardNumber: 'Virtual card number',
physicalCardNumber: 'Physical card number',
getPhysicalCard: 'Get physical card',
reportFraud: 'Report virtual card fraud',
reviewTransaction: 'Review transaction',
suspiciousBannerTitle: 'Suspicious transaction',
Expand Down Expand Up @@ -903,6 +904,27 @@ export default {
thatDidntMatch: "That didn't match the last 4 digits on your card. Please try again.",
},
},
getPhysicalCard: {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@grgia

Can I get this translated to Spanish, please?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bump @grgia

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might have some of these already and could move them to common

App/src/languages/en.ts

Lines 1044 to 1046 in 9e35d91

legalName: 'Legal name',
legalFirstName: 'Legal first name',
legalLastName: 'Legal last name',

header: 'Get physical card',
nameMessage: 'Enter your first and last name, as this will be shown on your card.',
legalName: 'Legal name',
legalFirstName: 'Legal first name',
legalLastName: 'Legal last name',
phoneMessage: 'Enter your phone number.',
phoneNumber: 'Phone number',
address: 'Address',
addressMessage: 'Enter your shipping address.',
streetAddress: 'Street Address',
city: 'City',
state: 'State',
zipPostcode: 'Zip/Postcode',
country: 'Country',
confirmMessage: 'Please confirm your details below.',
estimatedDeliveryMessage: 'Your physical card will arrive in 2-3 business days.',
next: 'Next',
getPhysicalCard: 'Get physical card',
shipCard: 'Ship card',
},
transferAmountPage: {
transfer: ({amount}: TransferParams) => `Transfer${amount ? ` ${amount}` : ''}`,
instant: 'Instant (Debit card)',
Expand Down
23 changes: 23 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -868,6 +868,7 @@ export default {
availableSpend: 'Límite restante',
virtualCardNumber: 'Número de la tarjeta virtual',
physicalCardNumber: 'Número de la tarjeta física',
getPhysicalCard: 'Obtener tarjeta física',
reportFraud: 'Reportar fraude con la tarjeta virtual',
reviewTransaction: 'Revisar transacción',
suspiciousBannerTitle: 'Transacción sospechosa',
Expand Down Expand Up @@ -899,6 +900,28 @@ export default {
thatDidntMatch: 'Los 4 últimos dígitos de tu tarjeta no coinciden. Por favor, inténtalo de nuevo.',
},
},
// TODO: add translation
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@joekaufmanexpensify I am still missing some spanish translations that I could not find in the codebase. Could you get them for me, please? 😄

getPhysicalCard: {
header: 'Obtener tarjeta física',
nameMessage: 'Introduce tu nombre y apellido como aparecerá en tu tarjeta.',
legalName: 'Nombre completo',
legalFirstName: 'Nombre legal',
legalLastName: 'Apellidos legales',
phoneMessage: 'Introduce tu número de teléfono.',
phoneNumber: 'Número de teléfono',
address: 'Dirección',
addressMessage: 'Introduce tu dirección de envío.',
streetAddress: 'Calle de dirección',
city: 'Ciudad',
state: 'Estado',
zipPostcode: 'Código postal',
country: 'País',
confirmMessage: 'Por favor confirma tus datos.',
estimatedDeliveryMessage: 'Tu tarjeta física llegará en 2-3 días laborales.',
next: 'Siguiente',
getPhysicalCard: 'Obtener tarjeta física',
shipCard: 'Enviar tarjeta',
},
transferAmountPage: {
transfer: ({amount}: TransferParams) => `Transferir${amount ? ` ${amount}` : ''}`,
instant: 'Instante',
Expand Down
Loading
Loading