From 51cd249f1b528ea754705ba0594012da6d72d1d6 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Tue, 27 Jun 2023 13:27:01 +0200 Subject: [PATCH 001/615] add preferences entry for color theme page --- src/pages/settings/Preferences/PreferencesPage.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/pages/settings/Preferences/PreferencesPage.js b/src/pages/settings/Preferences/PreferencesPage.js index 81b129439e96..44baf2be54c4 100755 --- a/src/pages/settings/Preferences/PreferencesPage.js +++ b/src/pages/settings/Preferences/PreferencesPage.js @@ -24,6 +24,9 @@ const propTypes = { /** The chat priority mode */ priorityMode: PropTypes.string, + /** The app's color theme */ + colorTheme: PropTypes.string, + /** The details about the user that is signed in */ user: PropTypes.shape({ /** Whether or not the user is subscribed to news updates */ @@ -39,12 +42,14 @@ const propTypes = { const defaultProps = { priorityMode: CONST.PRIORITY_MODE.DEFAULT, + colorTheme: CONST.COLOR_THEME.DARK, user: {}, }; function PreferencesPage(props) { const priorityModes = props.translate('priorityModePage.priorityModes'); const languages = props.translate('languagePage.languages'); + const colorThemes = props.translate('colorThemePage.colorThemes'); // Enable additional test features in the staging or dev environments const shouldShowTestToolMenu = _.contains([CONST.ENVIRONMENT.STAGING, CONST.ENVIRONMENT.ADHOC, CONST.ENVIRONMENT.DEV], props.environment); @@ -87,6 +92,13 @@ function PreferencesPage(props) { description={props.translate('languagePage.language')} onPress={() => Navigation.navigate(ROUTES.SETTINGS_LANGUAGE)} /> + Navigation.navigate(ROUTES.SETTINGS_COLOR_THEME)} + /> + {shouldShowTestToolMenu && ( @@ -109,6 +121,9 @@ export default compose( priorityMode: { key: ONYXKEYS.NVP_PRIORITY_MODE, }, + colorTheme: { + key: ONYXKEYS.COLOR_THEME, + }, user: { key: ONYXKEYS.USER, }, From bb6dae1a3a3db7238aff632474c84221cfdd2d56 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Wed, 5 Jul 2023 12:43:29 +0200 Subject: [PATCH 002/615] update theme naming --- .../settings/Preferences/PreferencesPage.js | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/pages/settings/Preferences/PreferencesPage.js b/src/pages/settings/Preferences/PreferencesPage.js index 44baf2be54c4..2e133e7c7802 100755 --- a/src/pages/settings/Preferences/PreferencesPage.js +++ b/src/pages/settings/Preferences/PreferencesPage.js @@ -25,7 +25,7 @@ const propTypes = { priorityMode: PropTypes.string, /** The app's color theme */ - colorTheme: PropTypes.string, + preferredTheme: PropTypes.string, /** The details about the user that is signed in */ user: PropTypes.shape({ @@ -42,14 +42,14 @@ const propTypes = { const defaultProps = { priorityMode: CONST.PRIORITY_MODE.DEFAULT, - colorTheme: CONST.COLOR_THEME.DARK, + preferredTheme: CONST.DEFAULT_THEME, user: {}, }; function PreferencesPage(props) { const priorityModes = props.translate('priorityModePage.priorityModes'); const languages = props.translate('languagePage.languages'); - const colorThemes = props.translate('colorThemePage.colorThemes'); + const themes = props.translate('themePage.themes'); // Enable additional test features in the staging or dev environments const shouldShowTestToolMenu = _.contains([CONST.ENVIRONMENT.STAGING, CONST.ENVIRONMENT.ADHOC, CONST.ENVIRONMENT.DEV], props.environment); @@ -94,11 +94,10 @@ function PreferencesPage(props) { /> Navigation.navigate(ROUTES.SETTINGS_COLOR_THEME)} + title={themes[props.preferredTheme].label} + description={props.translate('themePage.theme')} + onPress={() => Navigation.navigate(ROUTES.SETTINGS_THEME)} /> - {shouldShowTestToolMenu && ( @@ -121,8 +120,8 @@ export default compose( priorityMode: { key: ONYXKEYS.NVP_PRIORITY_MODE, }, - colorTheme: { - key: ONYXKEYS.COLOR_THEME, + preferredTheme: { + key: ONYXKEYS.PREFERRED_THEME, }, user: { key: ONYXKEYS.USER, From 0f6a0d99a4a2eaec8ce7fa2771b5f6d3b0b8ae43 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Tue, 11 Jul 2023 11:34:49 +0200 Subject: [PATCH 003/615] default to DEFAULT_THEME if onyx key is not set --- src/pages/settings/Preferences/PreferencesPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Preferences/PreferencesPage.js b/src/pages/settings/Preferences/PreferencesPage.js index 2e133e7c7802..5ec13453d05d 100755 --- a/src/pages/settings/Preferences/PreferencesPage.js +++ b/src/pages/settings/Preferences/PreferencesPage.js @@ -94,7 +94,7 @@ function PreferencesPage(props) { /> Navigation.navigate(ROUTES.SETTINGS_THEME)} /> From 75fbcbda9b17e96a43b5be153050cc89d3598516 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Wed, 16 Aug 2023 13:09:55 +0200 Subject: [PATCH 004/615] add routes --- src/ROUTES.js | 6 ++++++ src/libs/Navigation/linkingConfig.js | 15 +++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/ROUTES.js b/src/ROUTES.js index 6c0365e40568..0427a5fdce2e 100644 --- a/src/ROUTES.js +++ b/src/ROUTES.js @@ -14,6 +14,9 @@ const SETTINGS_PERSONAL_DETAILS = 'settings/profile/personal-details'; const SETTINGS_CONTACT_METHODS = 'settings/profile/contact-methods'; const SETTINGS_STATUS = 'settings/profile/status'; const SETTINGS_STATUS_SET = 'settings/profile/status/set'; +const SETTINGS_STATUS_CLEAR_AFTER = 'settings/profile/status/clear-after'; +const SETTINGS_STATUS_CLEAR_AFTER_CUSTOM = 'settings/profile/status/clear-after/custom'; +const SETTINGS_STATUS_CLEAR_AFTER_TIME = 'settings/profile/status/clear-after/time'; export default { BANK_ACCOUNT: 'bank-account', @@ -62,6 +65,9 @@ export default { SETTINGS_2FA_SUCCESS: 'settings/security/two-factor-auth/success', SETTINGS_STATUS, SETTINGS_STATUS_SET, + SETTINGS_STATUS_CLEAR_AFTER, + SETTINGS_STATUS_CLEAR_AFTER_CUSTOM, + SETTINGS_STATUS_CLEAR_AFTER_TIME, NEW_GROUP: 'new/group', NEW_CHAT: 'new/chat', NEW_TASK, diff --git a/src/libs/Navigation/linkingConfig.js b/src/libs/Navigation/linkingConfig.js index dcc4f77fde73..b7f42470870d 100644 --- a/src/libs/Navigation/linkingConfig.js +++ b/src/libs/Navigation/linkingConfig.js @@ -184,6 +184,21 @@ export default { path: ROUTES.SETTINGS_STATUS_SET, exact: true, }, + Settings_Status_Clear_After: { + path: ROUTES.SETTINGS_STATUS_CLEAR_AFTER, + }, + Settings_Status_Clear_After_Custom: { + path: ROUTES.SETTINGS_STATUS_CLEAR_AFTER_CUSTOM, + }, + Settings_Status_Clear_After_Time: { + path: ROUTES.SETTINGS_STATUS_CLEAR_AFTER_TIME, + }, + Settings_Status_SetTime: { + path: ROUTES.SETTINGS_STATUS_SET_TIME, + }, + Settings_Status_SetData: { + path: ROUTES.SETTINGS_STATUS_SET_DATE, + }, Workspace_Initial: { path: ROUTES.WORKSPACE_INITIAL, }, From cd8dd15f404a42e705f1ae2ebfa5ac40e2c9c160 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Wed, 16 Aug 2023 13:11:34 +0200 Subject: [PATCH 005/615] add disableIsFocusStyle feature to BaseSelectionListRadio --- .../SelectionListRadio/BaseSelectionListRadio.js | 1 + src/components/SelectionListRadio/RadioListItem.js | 11 ++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/components/SelectionListRadio/BaseSelectionListRadio.js b/src/components/SelectionListRadio/BaseSelectionListRadio.js index 4daacb184ea1..e9e832f05664 100644 --- a/src/components/SelectionListRadio/BaseSelectionListRadio.js +++ b/src/components/SelectionListRadio/BaseSelectionListRadio.js @@ -165,6 +165,7 @@ function BaseSelectionListRadio(props) { item={item} isFocused={isFocused} onSelectRow={props.onSelectRow} + disableIsFocusStyle={props.disableInitialFocusOptionStyle} /> ); }; diff --git a/src/components/SelectionListRadio/RadioListItem.js b/src/components/SelectionListRadio/RadioListItem.js index c5c4b3aeaf2c..d15a78cb9ed3 100644 --- a/src/components/SelectionListRadio/RadioListItem.js +++ b/src/components/SelectionListRadio/RadioListItem.js @@ -18,15 +18,20 @@ const propTypes = { /** Callback to fire when the item is pressed */ onSelectRow: PropTypes.func, + + /** Whether to disable the isFocused style */ + disableIsFocusStyle: PropTypes.bool, }; const defaultProps = { item: {}, isFocused: false, onSelectRow: () => {}, + disableIsFocusStyle: false, }; function RadioListItem(props) { + const isFocused = !props.disableIsFocusStyle && props.isFocused return ( props.onSelectRow(props.item)} @@ -36,14 +41,14 @@ function RadioListItem(props) { hoverStyle={styles.hoveredComponentBG} focusStyle={styles.hoveredComponentBG} > - + - + {props.item.text} {Boolean(props.item.alternateText) && ( - + {props.item.alternateText} )} From 3ecbaaf93889a19406abe7d5d8d7c7daaf818a27 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Wed, 16 Aug 2023 13:13:05 +0200 Subject: [PATCH 006/615] add translations --- src/languages/en.js | 8 ++++++++ src/languages/es.js | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/src/languages/en.js b/src/languages/en.js index 09651e6ec05e..7e787be1913c 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -874,6 +874,14 @@ export default { clearStatus: 'Clear status', save: 'Save', message: 'Message', + timePeriods: { + never: 'Never', + thirtyMinutes: 'Thirty minutes', + oneHour: 'One hour', + afterToday: 'After today', + afterWeek: 'After week', + custom: 'Custom', + } }, stepCounter: ({step, total, text}) => { let result = `Step ${step}`; diff --git a/src/languages/es.js b/src/languages/es.js index fa920c84ce35..c67a91897db7 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -878,6 +878,14 @@ export default { clearStatus: 'Borrar estado', save: 'Guardar', message: 'Mensaje', + timePeriods: { + never: 'Nunca', + thirtyMinutes: 'Treinta minutos', + oneHour: 'Una hora', + afterToday: 'Después de hoy', + afterWeek: 'Después de una semana', + custom: 'Personalizado', + }, }, stepCounter: ({step, total, text}) => { let result = `Paso ${step}`; From 244147e147f86d28bf73a7f706ad393d79f3248c Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Wed, 16 Aug 2023 13:15:42 +0200 Subject: [PATCH 007/615] extend dateUtils --- src/libs/DateUtils.js | 90 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/src/libs/DateUtils.js b/src/libs/DateUtils.js index 6be627dc643d..aaa9f41310dd 100644 --- a/src/libs/DateUtils.js +++ b/src/libs/DateUtils.js @@ -205,6 +205,87 @@ function getDateStringFromISOTimestamp(isoTimestamp) { return dateString; } +/** + * @returns {string} example: 2023-05-16 + */ +function getThirtyMinutesFromNow() { + return moment().add(30, 'minutes').format('YYYY-MM-DD HH:mm:ss'); +} + +function getOneHourFromNow() { + return moment().add(1, 'hours').format('YYYY-MM-DD HH:mm:ss'); +} + +/** + * @returns {string} example: 2023-05-16 + */ +function getEndOfToday() { + return moment().endOf('day').format('YYYY-MM-DD HH:mm:ss'); +} + +/** + * @returns {string} example: 2023-05-16 + */ +function getEndOfWeekFromNow() { + return moment().add(7, 'days').format('YYYY-MM-DD HH:mm:ss'); +} + +/** + * @returns {string} example: 2023-05-16 + * it's kind of 'newer' date + */ +function getOneHundredYearsFromNow() { + return moment().endOf(100, 'years').format('YYYY-MM-DD HH:mm:ss'); +} + +/** + * @param {string} time + * @returns {string} example: 2023-05-16 + * it's kind of 'newer' date + */ +function extractDate(time) { + return moment(time).format('YYYY-MM-DD'); +} + +/** + * @param {string} dateTimeString + * @returns {string} example: 2023-05-16 + * it's kind of 'newer' date + */ +function extractTime(dateTimeString) { + return moment(dateTimeString).format('HH:mm'); +} + +/** + * @param {string} dateTimeString + * @returns {string} example: 2023-05-16 + * it's kind of 'newer' date + */ +function formatDateTo12Hour(dateTimeString) { + if (!dateTimeString) return ''; + return moment(dateTimeString).format('YYYY-MM-DD hh:mm:ss A'); +} + +// /** +// * @returns {string} example: 2023-05-16 +// */ +function getDateBasedFromType(type) { + switch (type) { + case CONST.CUSTOM_STATUS_TYPES.THIRTY_MINUTES: + return getThirtyMinutesFromNow(); + case CONST.CUSTOM_STATUS_TYPES.ONE_HOUR: + return getOneHourFromNow(); + case CONST.CUSTOM_STATUS_TYPES.AFTER_TODAY: + return getEndOfToday(); + case CONST.CUSTOM_STATUS_TYPES.AFTER_WEEK: + return getEndOfWeekFromNow(); + case CONST.CUSTOM_STATUS_TYPES.NEVER: + return getOneHundredYearsFromNow(); + default: + return ''; + } +} + /** * @namespace DateUtils */ @@ -220,6 +301,15 @@ const DateUtils = { getDBTime, subtractMillisecondsFromDateTime, getDateStringFromISOTimestamp, + getThirtyMinutesFromNow, + getEndOfToday, + getEndOfWeekFromNow, + getOneHundredYearsFromNow, + getDateBasedFromType, + getOneHourFromNow, + extractDate, + extractTime, + formatDateTo12Hour, }; export default DateUtils; From b6125582777998b043b518789789ba179ed9168a Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Wed, 16 Aug 2023 13:17:27 +0200 Subject: [PATCH 008/615] add new field for draftCustomStatus in Onyx --- src/libs/actions/FormActions.js | 10 +++++++++- src/libs/actions/User.js | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/FormActions.js b/src/libs/actions/FormActions.js index 2a4e1dbd5d27..9372e32fd733 100644 --- a/src/libs/actions/FormActions.js +++ b/src/libs/actions/FormActions.js @@ -32,4 +32,12 @@ function setDraftValues(formID, draftValues) { Onyx.merge(`${formID}Draft`, draftValues); } -export {setIsLoading, setErrors, setErrorFields, setDraftValues}; +/** + * @param {String} formID + * @param {Object} draftValues + */ +function cleanDraftValues(formID) { + Onyx.merge(`${formID}Draft`, null); +} + +export {setIsLoading, setErrors, setErrorFields, setDraftValues, cleanDraftValues}; diff --git a/src/libs/actions/User.js b/src/libs/actions/User.js index 7f7078c45379..6bd0583d8d05 100644 --- a/src/libs/actions/User.js +++ b/src/libs/actions/User.js @@ -876,7 +876,7 @@ function updateDraftCustomStatus(status) { * */ function clearDraftCustomStatus() { - Onyx.merge(ONYXKEYS.CUSTOM_STATUS_DRAFT, {text: '', emojiCode: '', clearAfter: ''}); + Onyx.merge(ONYXKEYS.CUSTOM_STATUS_DRAFT, {text: '', emojiCode: '', clearAfter: '', customDateTemporary: ''}); } export { From f774e8e68f2ab9b31aaa8c337b6bc363fb7c5adc Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Wed, 16 Aug 2023 13:18:12 +0200 Subject: [PATCH 009/615] use proper data for title on StatusPage --- .../settings/Profile/CustomStatus/StatusPage.js | 17 ++++++++++++++--- src/styles/styles.js | 12 +++++++++++- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/pages/settings/Profile/CustomStatus/StatusPage.js b/src/pages/settings/Profile/CustomStatus/StatusPage.js index c239f9e45f2d..ec189e2bbc59 100644 --- a/src/pages/settings/Profile/CustomStatus/StatusPage.js +++ b/src/pages/settings/Profile/CustomStatus/StatusPage.js @@ -20,6 +20,7 @@ import styles from '../../../../styles/styles'; import compose from '../../../../libs/compose'; import ONYXKEYS from '../../../../ONYXKEYS'; import ROUTES from '../../../../ROUTES'; +import DateUtils from '../../../../libs/DateUtils'; const propTypes = { ...withCurrentUserPersonalDetailsPropTypes, @@ -31,16 +32,20 @@ function StatusPage({draftStatus, currentUserPersonalDetails}) { const currentUserStatusText = lodashGet(currentUserPersonalDetails, 'status.text', ''); const draftEmojiCode = lodashGet(draftStatus, 'emojiCode'); const draftText = lodashGet(draftStatus, 'text'); + const draftClearAfter = lodashGet(draftStatus, 'clearAfter'); const defaultEmoji = draftEmojiCode || currentUserEmojiCode; const defaultText = draftEmojiCode ? draftText : currentUserStatusText; const customStatus = draftEmojiCode ? `${draftEmojiCode} ${draftText}` : `${currentUserEmojiCode || ''} ${currentUserStatusText || ''}`; + const hasDraftStatus = !!draftEmojiCode || !!draftText; + const formattedClearAfter = DateUtils.formatDateTo12Hour(lodashGet(currentUserPersonalDetails, 'status.clearAfter', '')); + const formattedClearAfterDraft = DateUtils.formatDateTo12Hour(draftClearAfter); + const customClearAfter = draftClearAfter ? formattedClearAfterDraft : formattedClearAfter ||''; const updateStatus = useCallback(() => { - const endOfDay = moment().endOf('day').toDate(); - User.updateCustomStatus({text: defaultText, emojiCode: defaultEmoji, clearAfter: endOfDay.toISOString()}); - + const endOfDay = moment().endOf('day').format('YYYY-MM-DD HH:mm:ss'); + User.updateCustomStatus({text: defaultText, emojiCode: defaultEmoji, clearAfter: endOfDay}); User.clearDraftCustomStatus(); Navigation.goBack(ROUTES.SETTINGS_PROFILE); }, [defaultText, defaultEmoji]); @@ -83,6 +88,12 @@ function StatusPage({draftStatus, currentUserPersonalDetails}) { inputID="test" onPress={() => Navigation.navigate(ROUTES.SETTINGS_STATUS_SET)} /> + Navigation.navigate(ROUTES.SETTINGS_STATUS_CLEAR_AFTER)} + /> {(!!currentUserEmojiCode || !!currentUserStatusText) && ( Date: Wed, 16 Aug 2023 14:43:59 +0200 Subject: [PATCH 010/615] WIP StatusClearAfterPage --- src/ONYXKEYS.js | 1 + .../AppNavigator/ModalStackNavigators.js | 7 + .../CustomStatus/StatusClearAfterPage.js | 145 ++++++++++++++++++ 3 files changed, 153 insertions(+) create mode 100644 src/pages/settings/Profile/CustomStatus/StatusClearAfterPage.js diff --git a/src/ONYXKEYS.js b/src/ONYXKEYS.js index a99a09063561..a7b148212ece 100755 --- a/src/ONYXKEYS.js +++ b/src/ONYXKEYS.js @@ -225,6 +225,7 @@ export default { SETTINGS_STATUS_SET_FORM: 'settingsStatusSetForm', SETTINGS_STATUS_CLEAR_AFTER_FORM: 'settingsStatusClearAfterForm', SETTINGS_STATUS_SET_CLEAR_AFTER_FORM: 'settingsStatusSetClearAfterForm', + SETTINGS_STATUS_CLEAR_DATE_FORM: 'settingsStatusClearDateForm', }, // Whether we should show the compose input or not diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js index 8a489afb035e..ee9488884f29 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js @@ -529,6 +529,13 @@ const SettingsModalStackNavigator = createModalStackNavigator([ }, name: 'Settings_Status_Set', }, + { + getComponent: () => { + const SettingsStatusClearAfter = require('../../../pages/settings/Profile/CustomStatus/StatusClearAfterPage').default; + return SettingsStatusClearAfter; + }, + name: 'Settings_Status_Clear_After', + }, { getComponent: () => { const WorkspaceInitialPage = require('../../../pages/workspace/WorkspaceInitialPage').default; diff --git a/src/pages/settings/Profile/CustomStatus/StatusClearAfterPage.js b/src/pages/settings/Profile/CustomStatus/StatusClearAfterPage.js new file mode 100644 index 000000000000..4a30e98c87d1 --- /dev/null +++ b/src/pages/settings/Profile/CustomStatus/StatusClearAfterPage.js @@ -0,0 +1,145 @@ +import React, {useEffect, useState, useCallback, useMemo} from 'react'; +import {View} from 'react-native'; +import _ from 'lodash'; +import {withOnyx} from 'react-native-onyx'; +import lodashGet from 'lodash/get'; +import ScreenWrapper from '../../../../components/ScreenWrapper'; +import Form from '../../../../components/Form'; +import HeaderWithBackButton from '../../../../components/HeaderWithBackButton'; +import ROUTES from '../../../../ROUTES'; +import Navigation from '../../../../libs/Navigation/Navigation'; +import styles from '../../../../styles/styles'; +import Text from '../../../../components/Text'; +import MenuItemWithTopDescription from '../../../../components/MenuItemWithTopDescription'; +import SelectionListRadio from '../../../../components/SelectionListRadio'; +import useLocalize from '../../../../hooks/useLocalize'; +import ONYXKEYS from '../../../../ONYXKEYS'; +import CONST from '../../../../CONST'; +import * as User from '../../../../libs/actions/User'; +import withLocalize, {withLocalizePropTypes} from '../../../../components/withLocalize'; +import compose from '../../../../libs/compose'; +import DateUtils from '../../../../libs/DateUtils'; +import withCurrentUserPersonalDetails from '../../../../components/withCurrentUserPersonalDetails'; + +const defaultProps = {}; + +const propTypes = {}; + +function StatusClearAfterPage({currentUserPersonalDetails, customStatus, ...props}) { + const localize = useLocalize(); + const clearAfter = lodashGet(currentUserPersonalDetails, 'status.clearAfter', ''); + + const draftClearAfter = lodashGet(customStatus, 'clearAfter', ''); + const customDateTemporary = lodashGet(customStatus, 'customDateTemporary', ''); + const [draftPeriod, setDraftPeriod] = useState((clearAfter && !draftClearAfter) || draftClearAfter ? CONST.CUSTOM_STATUS_TYPES.CUSTOM : CONST.CUSTOM_STATUS_TYPES.AFTER_TODAY); + + const localesToThemes = useMemo( + () => + _.map(CONST.CUSTOM_STATUS_TYPES, (value, key) => ({ + value, + text: localize.translate(`statusPage.timePeriods.${value}`), + keyForList: key, + isSelected: draftPeriod === value, + })), + [draftPeriod], + ); + + const onSubmit = () => { + let calculatedDraftDate = ''; + if (draftPeriod === CONST.CUSTOM_STATUS_TYPES.CUSTOM) { + calculatedDraftDate = customDateTemporary || draftClearAfter; + } else { + const selectedRange = localesToThemes.find((item) => item.isSelected); + calculatedDraftDate = DateUtils.getDateBasedFromType(selectedRange.value); + } + + User.updateDraftCustomStatus({clearAfter: calculatedDraftDate}); + Navigation.goBack(ROUTES.SETTINGS_STATUS); + }; + + const updateMode = useCallback((mode) => { + if (mode.value === CONST.CUSTOM_STATUS_TYPES.CUSTOM) { + User.updateDraftCustomStatus({customDateTemporary: DateUtils.getOneHourFromNow()}); + } + setDraftPeriod(mode.value); + }, []); + + useEffect(() => { + if ((clearAfter && !draftClearAfter) || draftClearAfter) { + User.updateDraftCustomStatus({customDateTemporary: (clearAfter && !draftClearAfter) || draftClearAfter}); + return; + } + // value by default is CONST.CUSTOM_STATUS_TYPES.AFTER_TODAY + User.updateDraftCustomStatus({customDate: DateUtils.getEndOfToday()}); + }, []); + + const customStatusDate = DateUtils.extractDate(customDateTemporary || draftClearAfter || clearAfter); + const customStatusTime = DateUtils.extractTime(customDateTemporary || draftClearAfter || clearAfter); + return ( + + Navigation.goBack(ROUTES.SETTINGS_STATUS)} + /> + When should we clear your status? +
{}} + onBackButtonPress={() => {}} + submitButtonText="Save" + onSubmit={onSubmit} + style={[{flexGrow: 1}]} + scrollContextEnabled={false} + > + + + + {draftPeriod === CONST.CUSTOM_STATUS_TYPES.CUSTOM && ( + <> + Navigation.navigate(ROUTES.SETTINGS_STATUS_CLEAR_AFTER_CUSTOM)} + /> + Navigation.navigate(ROUTES.SETTINGS_STATUS_CLEAR_AFTER_TIME)} + /> + + )} + +
+
+ ); +} + +StatusClearAfterPage.displayName = 'StatusClearAfterPage'; +StatusClearAfterPage.propTypes = propTypes; +StatusClearAfterPage.defaultProps = defaultProps; + +export default compose( + withCurrentUserPersonalDetails, + withLocalize, + withOnyx({ + timePeriodType: { + key: `${ONYXKEYS.FORMS.SETTINGS_STATUS_SET_CLEAR_AFTER_FORM}Draft`, + }, + clearDateForm: { + key: `${ONYXKEYS.FORMS.SETTINGS_STATUS_CLEAR_DATE_FORM}Draft`, + }, + customStatus: { + key: ONYXKEYS.CUSTOM_STATUS_DRAFT, + }, + preferredLocale: { + key: ONYXKEYS.NVP_PREFERRED_LOCALE, + }, + }), +)(StatusClearAfterPage); From 76f5a67b0017f1716fdd84826c174b1354ecd18d Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Wed, 16 Aug 2023 14:46:42 +0200 Subject: [PATCH 011/615] add style property into BaseSelectionListRadio --- src/components/SelectionListRadio/BaseSelectionListRadio.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/SelectionListRadio/BaseSelectionListRadio.js b/src/components/SelectionListRadio/BaseSelectionListRadio.js index e9e832f05664..95c3e79b87fa 100644 --- a/src/components/SelectionListRadio/BaseSelectionListRadio.js +++ b/src/components/SelectionListRadio/BaseSelectionListRadio.js @@ -215,7 +215,7 @@ function BaseSelectionListRadio(props) { > {({safeAreaPaddingBottomStyle}) => ( - + {shouldShowTextInput && ( Date: Wed, 16 Aug 2023 14:49:43 +0200 Subject: [PATCH 012/615] WIP SettingsStatusCustomClearAfter --- .../AppNavigator/ModalStackNavigators.js | 7 ++ .../CustomStatus/CustomClearAfterPage.js | 92 +++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 src/pages/settings/Profile/CustomStatus/CustomClearAfterPage.js diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js index ee9488884f29..ae8d5dafa0d6 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js @@ -536,6 +536,13 @@ const SettingsModalStackNavigator = createModalStackNavigator([ }, name: 'Settings_Status_Clear_After', }, + { + getComponent: () => { + const SettingsStatusCustomClearAfter = require('../../../pages/settings/Profile/CustomStatus/CustomClearAfterPage').default; + return SettingsStatusCustomClearAfter; + }, + name: 'Settings_Status_Clear_After_Custom', + }, { getComponent: () => { const WorkspaceInitialPage = require('../../../pages/workspace/WorkspaceInitialPage').default; diff --git a/src/pages/settings/Profile/CustomStatus/CustomClearAfterPage.js b/src/pages/settings/Profile/CustomStatus/CustomClearAfterPage.js new file mode 100644 index 000000000000..a67f4b360ffe --- /dev/null +++ b/src/pages/settings/Profile/CustomStatus/CustomClearAfterPage.js @@ -0,0 +1,92 @@ +import moment from 'moment'; +import PropTypes from 'prop-types'; +import React from 'react'; +import {withOnyx} from 'react-native-onyx'; +import lodashGet from 'lodash/get'; +import ONYXKEYS from '../../../../ONYXKEYS'; +import ROUTES from '../../../../ROUTES'; +import Form from '../../../../components/Form'; +import HeaderWithBackButton from '../../../../components/HeaderWithBackButton'; +import NewDatePicker from '../../../../components/NewDatePicker'; +import ScreenWrapper from '../../../../components/ScreenWrapper'; +import withLocalize, {withLocalizePropTypes} from '../../../../components/withLocalize'; +import Navigation from '../../../../libs/Navigation/Navigation'; +import * as User from '../../../../libs/actions/User'; +import compose from '../../../../libs/compose'; +import styles from '../../../../styles/styles'; +import usePrivatePersonalDetails from '../../../../hooks/usePrivatePersonalDetails'; +import DateUtils from '../../../../libs/DateUtils'; + +const propTypes = { + /* Onyx Props */ + + /** User's private personal details */ + privatePersonalDetails: PropTypes.shape({ + dob: PropTypes.string, + }), + + ...withLocalizePropTypes, +}; + +const defaultProps = { + privatePersonalDetails: { + dob: '', + }, +}; + +function CustomClearAfterPage({translate, customStatus, clearDateForm, personalDetails}) { + usePrivatePersonalDetails(); + const cleanAfter = lodashGet(personalDetails, 'status.cleanAfter', ''); + const defaultValue = cleanAfter || DateUtils.extractDate(customStatus?.customDateTemporary); + + + const onSubmit = (v) => { + User.updateDraftCustomStatus({customDateTemporary: v.dob}); + Navigation.goBack(ROUTES.SETTINGS_STATUS_CLEAR_AFTER); + }; + + return ( + + Navigation.goBack(ROUTES.SETTINGS_STATUS_CLEAR_AFTER)} + /> +
+ + +
+ ); +} + +CustomClearAfterPage.propTypes = propTypes; +CustomClearAfterPage.defaultProps = defaultProps; +CustomClearAfterPage.displayName = 'CustomClearAfterPage'; + +export default compose( + withLocalize, + withOnyx({ + privatePersonalDetails: { + key: ONYXKEYS.PRIVATE_PERSONAL_DETAILS, + }, + customStatus: { + key: ONYXKEYS.CUSTOM_STATUS_DRAFT, + }, + clearDateForm: { + key: `${ONYXKEYS.FORMS.SETTINGS_STATUS_CLEAR_DATE_FORM}Draft`, + }, + }), +)(CustomClearAfterPage); From dbfde80e6f987252a3d0b53fc27a193c0b26bf2e Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Mon, 21 Aug 2023 17:34:44 +0200 Subject: [PATCH 013/615] lint --- .../SelectionListRadio/RadioListItem.js | 2 +- src/languages/en.js | 12 +- src/languages/es.js | 2361 +++++++++-------- .../Profile/CustomStatus/StatusPage.js | 3 +- 4 files changed, 1189 insertions(+), 1189 deletions(-) diff --git a/src/components/SelectionListRadio/RadioListItem.js b/src/components/SelectionListRadio/RadioListItem.js index a538bf713b11..ab1272e50910 100644 --- a/src/components/SelectionListRadio/RadioListItem.js +++ b/src/components/SelectionListRadio/RadioListItem.js @@ -35,7 +35,7 @@ const defaultProps = { }; function RadioListItem(props) { - const isFocused = !props.disableIsFocusStyle && props.isFocused + const isFocused = !props.disableIsFocusStyle && props.isFocused; return ( props.onSelectRow(props.item)} diff --git a/src/languages/en.js b/src/languages/en.js index a6b16d3d089c..094487369f8a 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -895,12 +895,12 @@ export default { save: 'Save', message: 'Message', timePeriods: { - never: 'Never', - thirtyMinutes: 'Thirty minutes', - oneHour: 'One hour', - afterToday: 'After today', - afterWeek: 'After week', - custom: 'Custom', + never: 'Never', + thirtyMinutes: 'Thirty minutes', + oneHour: 'One hour', + afterToday: 'After today', + afterWeek: 'After week', + custom: 'Custom', }, untilTomorrow: 'Until tomorrow', untilTime: ({time}) => `Until ${time}`, diff --git a/src/languages/es.js b/src/languages/es.js index ecbc13c65648..37ef60152c6c 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -905,7 +905,7 @@ export default { afterToday: 'Después de hoy', afterWeek: 'Después de una semana', custom: 'Personalizado', - untilTomorrow: 'Hasta mañana', + untilTomorrow: 'Hasta mañana', }, untilTime: ({time}) => { // Check for HH:MM AM/PM format and starts with '01:' @@ -923,1198 +923,1199 @@ export default { // Default case return `Hasta ${time}`; }, - stepCounter: ({step, total, text}) => { - let result = `Paso ${step}`; + stepCounter: ({step, total, text}) => { + let result = `Paso ${step}`; - if (total) { - result = `${result} de ${total}`; - } + if (total) { + result = `${result} de ${total}`; + } - if (text) { - result = `${result}: ${text}`; - } - return result; - }, - bankAccount: { - accountNumber: 'Número de cuenta', - routingNumber: 'Número de ruta', - addBankAccount: 'Añadir cuenta bancaria', - chooseAnAccount: 'Elige una cuenta', - connectOnlineWithPlaid: 'Conéctate a Plaid online', - connectManually: 'Conectar manualmente', - desktopConnection: 'Para conectarse con Chase, Wells Fargo, Capital One o Bank of America, haga clic aquí para completar este proceso en un navegador.', - yourDataIsSecure: 'Tus datos están seguros', - toGetStarted: 'Añade una cuenta bancaria y emite tarjetas corporativas, reembolsa gastos y cobra y paga facturas, todo desde un mismo lugar.', - plaidBodyCopy: 'Ofrezca a sus empleados una forma más sencilla de pagar - y recuperar - los gastos de la empresa.', - checkHelpLine: 'Su número de ruta y número de cuenta se pueden encontrar en un cheque de la cuenta bancaria.', - validateAccountError: - 'Para terminar de configurar tu cuenta bancaria, debes validar tu cuenta de Expensify. Por favor, revisa tu correo electrónico para validar tu cuenta y vuelve aquí para continuar.', - hasPhoneLoginError: - 'Para añadir una cuenta bancaria verificada, asegúrate de que tu nombre de usuario principal sea un correo electrónico válido y vuelve a intentarlo. Puedes añadir tu número de teléfono como nombre de usuario secundario.', - hasBeenThrottledError: 'Se produjo un error al intentar añadir tu cuenta bancaria. Por favor, espera unos minutos e inténtalo de nuevo.', - hasCurrencyError: - '¡Ups! Parece que la moneda de tu espacio de trabajo está configurada en una moneda diferente a USD. Para continuar, por favor configúrala en USD e inténtalo nuevamente.', - error: { - noBankAccountAvailable: 'Lo sentimos, no hay ninguna cuenta bancaria disponible', - noBankAccountSelected: 'Por favor, elige una cuenta bancaria', - taxID: 'Por favor, introduce un número de identificación fiscal válido', - website: 'Por favor, introduce un sitio web válido', - zipCode: 'Por favor, introduce un código postal válido', - phoneNumber: 'Por favor, introduce un teléfono válido', - companyName: 'Por favor, introduce un nombre comercial legal válido', - addressCity: 'Por favor, introduce una ciudad válida', - addressStreet: 'Por favor, introduce una calle de dirección válida que no sea un apartado postal', - addressState: 'Por favor, selecciona un estado', - incorporationDateFuture: 'La fecha de incorporación no puede ser futura', - incorporationState: 'Por favor, selecciona una estado válido', - industryCode: 'Por favor, introduce un código de clasificación de industria válido', - restrictedBusiness: 'Por favor, confirma que la empresa no está en la lista de negocios restringidos', - routingNumber: 'Por favor, introduce un número de ruta válido', - accountNumber: 'Por favor, introduce un número de cuenta válido', - routingAndAccountNumberCannotBeSame: 'El número de ruta y el número de cuenta no pueden ser iguales', - companyType: 'Por favor, selecciona un tipo de compañía válido', - tooManyAttempts: - 'Debido a la gran cantidad de intentos de inicio de sesión, esta opción se ha desactivado temporalmente durante 24 horas. Vuelve a intentarlo más tarde o introduce los detalles manualmente.', - address: 'Por favor, introduce una dirección válida', - dob: 'Por favor, selecciona una fecha de nacimiento válida', - age: 'Debe ser mayor de 18 años', - ssnLast4: 'Por favor, introduce los últimos 4 dígitos del número de seguridad social', - firstName: 'Por favor, introduce el nombre', - lastName: 'Por favor, introduce los apellidos', - noDefaultDepositAccountOrDebitCardAvailable: 'Por favor, añade una cuenta bancaria para depósitos o una tarjeta de débito', - validationAmounts: 'Los importes de validación que introduciste son incorrectos. Por favor, comprueba tu cuenta bancaria e inténtalo de nuevo.', - }, - }, - addPersonalBankAccountPage: { - enterPassword: 'Escribe tu contraseña de Expensify', - alreadyAdded: 'Esta cuenta ya ha sido añadida.', - chooseAccountLabel: 'Cuenta', - successTitle: '¡Cuenta bancaria personal añadida!', - successMessage: 'Enhorabuena, tu cuenta bancaria está lista para recibir reembolsos.', - }, - attachmentView: { - unknownFilename: 'Archivo desconocido', - passwordRequired: 'Por favor, introduce tu contraseña', - passwordIncorrect: 'Contraseña incorrecta. Por favor, inténtalo de nuevo.', - failedToLoadPDF: 'Hubo un error al intentar cargar el PDF.', - pdfPasswordForm: { - title: 'PDF protegido con contraseña', - infoText: 'Este PDF esta protegido con contraseña.', - beforeLinkText: 'Por favor', - linkText: 'introduce la contraseña', - afterLinkText: 'para verlo.', - formLabel: 'Ver PDF', - }, - }, - messages: { - errorMessageInvalidPhone: `Por favor, introduce un número de teléfono válido sin paréntesis o guiones. Si reside fuera de Estados Unidos, por favor incluye el prefijo internacional (p. ej. ${CONST.EXAMPLE_PHONE_NUMBER}).`, - errorMessageInvalidEmail: 'Email inválido', - userIsAlreadyMemberOfWorkspace: ({login, workspace}) => `${login} ya es miembro de ${workspace}`, - }, - onfidoStep: { - acceptTerms: 'Al continuar con la solicitud para activar su billetera Expensify, confirma que ha leído, comprende y acepta ', - facialScan: 'Política y lanzamiento de la exploración facial de Onfido', - tryAgain: 'Intentar otra vez', - verifyIdentity: 'Verificar identidad', - genericError: 'Hubo un error al procesar este paso. Inténtalo de nuevo.', - cameraPermissionsNotGranted: 'No has habilitado los permisos para acceder a la cámara', - cameraRequestMessage: 'No has habilitado los permisos para acceder a la cámara. Necesitamos acceso para completar la verificaciôn.', - originalDocumentNeeded: 'Por favor, sube una imagen original de tu identificación en lugar de una captura de pantalla o imagen escaneada.', - documentNeedsBetterQuality: - 'Parece que tu identificación esta dañado o le faltan características de seguridad. Por favor, sube una imagen de tu documento sin daños y que se vea completamente.', - imageNeedsBetterQuality: 'Hay un problema con la calidad de la imagen de tu identificación. Por favor, sube una nueva imagen donde el identificación se vea con claridad.', - selfieIssue: 'Hay un problema con tu selfie/video. Por favor, sube un nuevo selfie/video grabado en el momento', - selfieNotMatching: 'Tu selfie/video no concuerda con tu identificación. Por favor, sube un nuevo selfie/video donde se vea tu cara con claridad.', - selfieNotLive: 'Tu selfie/video no parece ser un selfie/video en vivo. Por favor, sube un selfie/video a tiempo real.', - }, - additionalDetailsStep: { - headerTitle: 'Detalles adicionales', - helpText: 'Necesitamos confirmar la siguiente información antes de que podamos procesar el pago.', - helpTextIdologyQuestions: 'Tenemos que preguntarte unas preguntas más para terminar de verificar tu identidad', - helpLink: 'Obtén más información sobre por qué necesitamos esto.', - legalFirstNameLabel: 'Primer nombre legal', - legalMiddleNameLabel: 'Segundo nombre legal', - legalLastNameLabel: 'Apellidos legales', - selectAnswer: 'Selecciona una respuesta.', - ssnFull9Error: 'Por favor, introduce los 9 dígitos de un número de seguridad social válido', - needSSNFull9: 'Estamos teniendo problemas para verificar tu número de seguridad social. Introduce los 9 dígitos del número de seguridad social.', - weCouldNotVerify: 'No se pudo verificar', - pleaseFixIt: 'Corrige esta información antes de continuar.', - failedKYCTextBefore: 'No se ha podido verificar correctamente tu identidad. Vuelve a intentarlo más tarde y comunicate con ', - failedKYCTextAfter: ' si tienes alguna pregunta.', - }, - termsStep: { - headerTitle: 'Condiciones y tarifas', - haveReadAndAgree: 'He leído y acepto recibir ', - electronicDisclosures: 'divulgaciones electrónicas', - agreeToThe: 'Estoy de acuerdo con la ', - walletAgreement: 'Acuerdo de billetera', - enablePayments: 'Habilitar pagos', - feeAmountZero: '$0', - monthlyFee: 'Cuota mensual', - inactivity: 'Inactividad', - electronicFundsInstantFee: '1.5%', - noOverdraftOrCredit: 'Sin función de sobregiro / crédito', - electronicFundsWithdrawal: 'Retiro electrónico de fondos', - standard: 'Estándar', - shortTermsForm: { - expensifyPaymentsAccount: 'La billetera Expensify es emitida por The Bancorp Bank.', - perPurchase: 'Por compra', - atmWithdrawal: 'Retiro de cajero automático', - cashReload: 'Recarga de efectivo', - inNetwork: 'en la red', - outOfNetwork: 'fuera de la red', - atmBalanceInquiry: 'Consulta de saldo de cajero automático', - inOrOutOfNetwork: '(dentro o fuera de la red)', - customerService: 'Servicio al cliente', - automatedOrLive: '(agente automatizado o en vivo)', - afterTwelveMonths: '(después de 12 meses sin transacciones)', - weChargeOneFee: 'Cobramos un tipo de tarifa.', - fdicInsurance: 'Sus fondos son elegibles para el seguro de la FDIC.', - generalInfo: 'Para obtener información general sobre cuentas prepagas, visite', - conditionsDetails: 'Encuentra detalles y condiciones para todas las tarifas y servicios visitando', - conditionsPhone: 'o llamando al +1 833-400-0904.', - instant: '(instantáneo)', - electronicFundsInstantFeeMin: '(mínimo $0.25)', - }, - longTermsForm: { - listOfAllFees: 'Una lista de todas las tarifas de la billetera Expensify', - typeOfFeeHeader: 'Tipo de tarifa', - feeAmountHeader: 'Importe de la tarifa', - moreDetailsHeader: 'Más detalles', - openingAccountTitle: 'Abrir una cuenta', - openingAccountDetails: 'No hay tarifa para abrir una cuenta.', - monthlyFeeDetails: 'No hay tarifa mensual.', - customerServiceTitle: 'Servicio al cliente', - customerServiceDetails: 'No hay tarifas de servicio al cliente.', - inactivityDetails: 'No hay tarifa de inactividad.', - sendingFundsTitle: 'Enviar fondos a otro titular de cuenta', - sendingFundsDetails: 'No se aplica ningún cargo por enviar fondos a otro titular de cuenta utilizando su saldo cuenta bancaria o tarjeta de débito', - electronicFundsStandardDetails: - 'No hay cargo por transferir fondos desde su billetera Expensify ' + - 'a su cuenta bancaria utilizando la opción estándar. Esta transferencia generalmente se completa en' + - '1-3 negocios días.', - electronicFundsInstantDetails: - 'Hay una tarifa para transferir fondos desde su billetera Expensify a ' + - 'su tarjeta de débito vinculada utilizando la opción de transferencia instantánea. Esta transferencia ' + - 'generalmente se completa dentro de varios minutos. La tarifa es el 1.5% del monto de la ' + - 'transferencia (con una tarifa mínima de $ 0.25). ', - fdicInsuranceBancorp: - 'Sus fondos son elegibles para el seguro de la FDIC. Sus fondos se mantendrán en o ' + - 'transferido a The Bancorp Bank, una institución asegurada por la FDIC. Una vez allí, sus fondos ' + - 'están asegurados a $ 250,000 por la FDIC en caso de que The Bancorp Bank quiebre. Ver', - fdicInsuranceBancorp2: 'para detalles.', - contactExpensifyPayments: 'Comuníquese con Expensify Payments llamando al + 1833-400-0904, por correoelectrónico a', - contactExpensifyPayments2: 'o inicie sesión en', - generalInformation: 'Para obtener información general sobre cuentas prepagas, visite', - generalInformation2: 'Si tiene una queja sobre una cuenta prepaga, llame al Consumer Financial Oficina de Protección al 1-855-411-2372 o visite', - printerFriendlyView: 'Ver versión para imprimir', - automated: 'Automatizado', - liveAgent: 'Agente en vivo', - instant: 'Instantáneo', - electronicFundsInstantFeeMin: 'Mínimo $0.25', - }, - }, - activateStep: { - headerTitle: 'Habilitar pagos', - activatedTitle: '¡Billetera activada!', - activatedMessage: 'Felicidades, tu Billetera está configurada y lista para hacer pagos.', - checkBackLaterTitle: 'Un momento...', - checkBackLaterMessage: 'Todavía estamos revisando tu información. Por favor, vuelva más tarde.', - continueToPayment: 'Continuar al pago', - continueToTransfer: 'Continuar a la transferencia', - }, - companyStep: { - headerTitle: 'Información de la empresa', - subtitle: '¡Ya casi estamos! Por motivos de seguridad, necesitamos confirmar la siguiente información:', - legalBusinessName: 'Nombre comercial legal', - companyWebsite: 'Página web de la empresa', - taxIDNumber: 'Número de identificación fiscal', - taxIDNumberPlaceholder: '9 dígitos', - companyType: 'Tipo de empresa', - incorporationDate: 'Fecha de incorporación', - incorporationState: 'Estado de incorporación', - industryClassificationCode: 'Código de clasificación industrial', - confirmCompanyIsNot: 'Confirmo que esta empresa no está en el', - listOfRestrictedBusinesses: 'lista de negocios restringidos', - incorporationDatePlaceholder: 'Fecha de inicio (aaaa-mm-dd)', - incorporationTypes: { - LLC: 'LLC', - CORPORATION: 'Corp', - PARTNERSHIP: 'Sociedad', - COOPERATIVE: 'Cooperativa', - SOLE_PROPRIETORSHIP: 'Propietario único', - OTHER: 'Otra', - }, - }, - requestorStep: { - headerTitle: 'Información personal', - subtitle: 'Dé más información sobre tí.', - learnMore: 'Más información', - isMyDataSafe: '¿Están seguros mis datos?', - onFidoConditions: 'Al continuar con la solicitud de añadir esta cuenta bancaria, confirma que ha leído, entiende y acepta ', - isControllingOfficer: 'Estoy autorizado a utilizar la cuenta bancaria de mi compañía para gastos de empresa', - isControllingOfficerError: 'Debe ser un oficial controlador con autorización para operar la cuenta bancaria de la compañía', - }, - validationStep: { - headerTitle: 'Validar cuenta bancaria', - buttonText: 'Finalizar configuración', - maxAttemptsReached: 'Se ha inhabilitado la validación de esta cuenta bancaria debido a demasiados intentos incorrectos.', - description: - 'Uno o dos días después de añadir tu cuenta a Expensify, te enviaremos tres (3) transacciones a tu cuenta. Tienen un nombre de comerciante similar a "Expensify, Inc. Validation".', - descriptionCTA: 'Introduce el importe de cada transacción en los campos siguientes. Ejemplo: 1.51.', - reviewingInfo: '¡Gracias! Estamos revisando tu información y nos comunicaremos contigo en breve. Consulta el chat con Concierge ', - forNextSteps: ' para conocer los próximos pasos para terminar de configurar tu cuenta bancaria.', - letsChatCTA: 'Sí, vamos a chatear', - letsChatText: 'Gracias. Necesitamos tu ayuda para verificar la información, pero podemos hacerlo rápidamente a través del chat. ¿Estás listo?', - letsChatTitle: '¡Vamos a chatear!', - enable2FATitle: 'Evita fraudes, activa la autenticación de dos factores!', - enable2FAText: - 'Tu seguridad es importante para nosotros. Por favor, configura ahora la autenticación de dos factores. Eso nos permitirá disputar las transacciones de la Tarjeta Expensify y reducirá tu riesgo de fraude.', - secureYourAccount: 'Asegura tu cuenta', - }, - beneficialOwnersStep: { - additionalInformation: 'Información adicional', - checkAllThatApply: 'Marca todos los que apliquen, en caso de que ninguno aplique dejar en blanco.', - iOwnMoreThan25Percent: 'Soy dueño de mas de 25% de ', - someoneOwnsMoreThan25Percent: 'Otra persona es dueña de mas de 25% de ', - additionalOwner: 'Beneficiario efectivo adicional', - removeOwner: 'Eliminar este beneficiario efectivo', - addAnotherIndividual: 'Añadir otra persona que es dueña de mas de 25% de ', - agreement: 'Acuerdo:', - termsAndConditions: 'Términos y condiciones', - certifyTrueAndAccurate: 'Certifico que la información dada es correcta', - error: { - certify: 'Debe certificar que la información es verdadera y precisa', - }, - }, - reimbursementAccountLoadingAnimation: { - oneMoment: 'Un momento', - explanationLine: 'Estamos verificando tu información y podrás continuar con los siguientes pasos en unos momentos.', - }, - session: { - offlineMessageRetry: 'Parece que estás desconectado. Por favor, comprueba tu conexión e inténtalo de nuevo.', - }, - workspace: { - common: { - card: 'Tarjetas', - workspace: 'Espacio de trabajo', - edit: 'Editar espacio de trabajo', - delete: 'Eliminar espacio de trabajo', - settings: 'Configuración', - reimburse: 'Reembolsos', - bills: 'Pagar facturas', - invoices: 'Enviar facturas', - travel: 'Viajes', - members: 'Miembros', - bankAccount: 'Cuenta bancaria', - connectBankAccount: 'Conectar cuenta bancaria', - testTransactions: 'Transacciones de prueba', - issueAndManageCards: 'Emitir y gestionar tarjetas', - reconcileCards: 'Reconciliar tarjetas', - settlementFrequency: 'Frecuencia de liquidación', - deleteConfirmation: '¿Estás seguro de que quieres eliminar este espacio de trabajo?', - unavailable: 'Espacio de trabajo no disponible', - memberNotFound: 'Miembro no encontrado. Para invitar a un nuevo miembro al espacio de trabajo, por favor, utiliza el botón Invitar que está arriba.', - notAuthorized: `No tienes acceso a esta página. ¿Estás tratando de unirte al espacio de trabajo? Comunícate con el propietario de este espacio de trabajo para que pueda añadirte como miembro. ¿Necesitas algo más? Comunícate con ${CONST.EMAIL.CONCIERGE}`, - goToRoom: ({roomName}) => `Ir a la sala ${roomName}`, - }, - emptyWorkspace: { - title: 'Crear un nuevo espacio de trabajo', - subtitle: 'En los espacios de trabajo es donde puedes chatear con tu equipo, reembolsar gastos, emitir tarjetas, enviar y pagar facturas y mas — todo en un mismo lugar', - features: { - trackAndCollect: 'Organiza recibos', - companyCards: 'Tarjetas de crédito corporativas', - reimbursements: 'Reembolsos fáciles', - }, - }, - new: { - newWorkspace: 'Nuevo espacio de trabajo', - getTheExpensifyCardAndMore: 'Consigue la Tarjeta Expensify y más', - }, - people: { - genericFailureMessage: 'Se ha producido un error al intentar eliminar a un usuario del espacio de trabajo. Por favor, inténtalo más tarde.', - removeMembersPrompt: '¿Estás seguro que quieres eliminar a los miembros seleccionados de tu espacio de trabajo?', - removeMembersTitle: 'Eliminar miembros', - selectAll: 'Seleccionar todo', - error: { - genericAdd: 'Ha ocurrido un problema al añadir el miembro al espacio de trabajo.', - cannotRemove: 'No puedes eliminarte ni a ti mismo ni al dueño del espacio de trabajo.', - genericRemove: 'Ha ocurrido un problema al eliminar al miembro del espacio de trabajo.', - }, - }, - card: { - header: 'Desbloquea Tarjetas Expensify gratis', - headerWithEcard: '¡Tus tarjetas están listas!', - noVBACopy: 'Conecta una cuenta bancaria para emitir tarjetas Expensify a los miembros de tu espacio de trabajo y accede a estos increíbles beneficios y más:', - VBANoECardCopy: - 'Añade tu correo electrónico de trabajo para emitir Tarjetas Expensify ilimitadas para los miembros de tu espacio de trabajo y acceder a todas estas increíbles ventajas:', - VBAWithECardCopy: 'Acceda a estos increíbles beneficios y más:', - benefit1: 'Hasta un 4% de devolución en tus gastos', - benefit2: 'Tarjetas digitales y físicas', - benefit3: 'Sin responsabilidad personal', - benefit4: 'Límites personalizables', - addWorkEmail: 'Añadir correo electrónico de trabajo', - checkingDomain: '¡Un momento! Estamos todavía trabajando para habilitar tu Tarjeta Expensify. Vuelve aquí en unos minutos.', - }, - reimburse: { - captureReceipts: 'Captura recibos', - fastReimbursementsHappyMembers: '¡Reembolsos rápidos = miembros felices!', - kilometers: 'Kilómetros', - miles: 'Millas', - viewAllReceipts: 'Ver todos los recibos', - reimburseReceipts: 'Reembolsar recibos', - trackDistance: 'Medir distancia', - trackDistanceCopy: 'Configura la tarifa y unidad usadas para medir distancias.', - trackDistanceRate: 'Tarifa', - trackDistanceUnit: 'Unidad', - unlockNextDayReimbursements: 'Desbloquea reembolsos diarios', - captureNoVBACopyBeforeEmail: 'Pide a los miembros de tu espacio de trabajo que envíen recibos a ', - captureNoVBACopyAfterEmail: ' y descarga la App de Expensify para controlar tus gastos en efectivo sobre la marcha.', - unlockNoVBACopy: 'Conecta una cuenta bancaria para reembolsar online a los miembros de tu espacio de trabajo.', - fastReimbursementsVBACopy: '¡Todo listo para reembolsar recibos desde tu cuenta bancaria!', - updateCustomUnitError: 'Los cambios no han podido ser guardados. El espacio de trabajo ha sido modificado mientras estabas desconectado. Por favor, inténtalo de nuevo.', - invalidRateError: 'Por favor, introduce una tarifa válida', - }, - bills: { - manageYourBills: 'Gestiona tus facturas', - askYourVendorsBeforeEmail: 'Pide a tus proveedores que envíen sus facturas a ', - askYourVendorsAfterEmail: ' y las escanearemos para que las pagues.', - viewAllBills: 'Ver facturas recibidas', - unlockOnlineBillPayment: 'Desbloquea el pago de facturas online', - unlockNoVBACopy: '¡Conecta tu cuenta bancaria para pagar tus facturas online de manera gratuita!', - hassleFreeBills: '¡Facturas sin complicaciones!', - VBACopy: '¡Todo listo para realizar pagos desde tu cuenta bancaria!', - }, - invoices: { - invoiceClientsAndCustomers: 'Emite facturas a tus clientes', - invoiceFirstSectionCopy: 'Envía facturas detalladas y profesionales directamente a tus clientes desde la app de Expensify.', - viewAllInvoices: 'Ver facturas emitidas', - unlockOnlineInvoiceCollection: 'Desbloquea el cobro de facturas online', - unlockNoVBACopy: 'Conecta tu cuenta bancaria para recibir pagos online de facturas - por transferencia o con tarjeta - directamente en tu cuenta.', - moneyBackInAFlash: '¡Tu dinero de vuelta en un momento!', - unlockVBACopy: '¡Todo listo para recibir pagos por transferencia o con tarjeta!', - viewUnpaidInvoices: 'Ver facturas emitidas pendientes', - sendInvoice: 'Enviar factura', - }, - travel: { - unlockConciergeBookingTravel: 'Desbloquea la reserva de viajes con Concierge', - noVBACopy: 'Conecta tu cuenta bancaria para permitir a los miembros de tu espacio de trabajo reservar sus vuelos, hoteles y coches empezando una conversación con Concierge.', - packYourBags: '¡Haz las maletas!', - VBACopy: '¡Miembros con la tarjeta Expensify pueden hablar con Concierge para reservar viajes!', - bookTravelWithConcierge: 'Reserva viajes con Concierge', - }, - invite: { - invitePeople: 'Invitar nuevos miembros', - genericFailureMessage: 'Se produjo un error al invitar al usuario al espacio de trabajo. Vuelva a intentarlo..', - pleaseEnterValidLogin: `Asegúrese de que el correo electrónico o el número de teléfono sean válidos (p. ej. ${CONST.EXAMPLE_PHONE_NUMBER}).`, - }, - inviteMessage: { - inviteMessageTitle: 'Añadir un mensaje', - inviteMessagePrompt: 'Añadir un mensaje para hacer tu invitación destacar', - personalMessagePrompt: 'Mensaje', - inviteNoMembersError: 'Por favor, selecciona al menos un miembro a invitar', - genericFailureMessage: 'Se produjo un error al invitar al usuario al espacio de trabajo. Vuelva a intentarlo..', - welcomeNote: ({workspaceName}) => - `¡Has sido invitado a ${workspaceName}! Descargue la aplicación móvil Expensify en use.expensify.com/download para comenzar a rastrear sus gastos.`, - }, - editor: { - nameInputLabel: 'Nombre', - nameInputHelpText: 'Este es el nombre que verás en tu espacio de trabajo.', - nameIsRequiredError: 'Debes definir un nombre para tu espacio de trabajo.', - nameIsTooLongError: `El nombre de su espacio de trabajo no puede tener más de ${CONST.WORKSPACE_NAME_CHARACTER_LIMIT} caracteres.`, - currencyInputLabel: 'Moneda por defecto', - currencyInputHelpText: 'Todas los gastos en este espacio de trabajo serán convertidos a esta moneda.', - currencyInputDisabledText: 'La moneda predeterminada no se puede cambiar porque este espacio de trabajo está vinculado a una cuenta bancaria en USD.', - save: 'Guardar', - genericFailureMessage: 'Se produjo un error al guardar el espacio de trabajo. Por favor, inténtalo de nuevo.', - avatarUploadFailureMessage: 'No se pudo subir el avatar. Por favor, inténtalo de nuevo.', + if (text) { + result = `${result}: ${text}`; + } + return result; }, bankAccount: { - continueWithSetup: 'Continuar con la configuración', - youreAlmostDone: - 'Casi has acabado de configurar tu cuenta bancaria, que te permitirá emitir tarjetas corporativas, reembolsar gastos y cobrar pagar facturas, todo desde la misma cuenta bancaria.', - streamlinePayments: 'Optimiza pagos', - oneMoreThing: '¡Una cosa más!', - allSet: '¡Todo listo!', - accountDescriptionNoCards: - 'Esta cuenta bancaria se utilizará para reembolsar gastos y cobrar y pagar facturas, todo desde la misma cuenta.\n\nPor favor, añade un correo electrónico de trabajo como tu nombre de usuario secundario para activar la Tarjeta Expensify.', - accountDescriptionWithCards: 'Esta cuenta bancaria se utilizará para emitir tarjetas corporativas, reembolsar gastos y cobrar y pagar facturas, todo desde la misma cuenta.', - addWorkEmail: 'Añadir correo electrónico de trabajo', - letsFinishInChat: '¡Continuemos en el chat!', - almostDone: '¡Casi listo!', - disconnectBankAccount: 'Desconectar cuenta bancaria', - noLetsStartOver: 'No, empecemos de nuevo', - startOver: 'Empezar de nuevo', - yesDisconnectMyBankAccount: 'Sí, desconecta mi cuenta bancaria', - yesStartOver: 'Sí, empezar de nuevo', - disconnectYour: 'Desconecta tu cuenta bancaria de ', - bankAccountAnyTransactions: '. Los reembolsos pendientes serán completados sin problemas.', - clearProgress: 'Empezar de nuevo descartará lo completado hasta ahora.', - areYouSure: '¿Estás seguro?', - workspaceCurrency: 'Moneda del espacio de trabajo', - updateCurrencyPrompt: - 'Parece que tu espacio de trabajo está configurado actualmente en una moneda diferente a USD. Por favor, haz clic en el botón de abajo para actualizar tu moneda a USD ahora.', - updateToUSD: 'Actualizar a USD', - }, - }, - getAssistancePage: { - title: 'Obtener ayuda', - subtitle: '¡Estamos aquí para ayudarte!', - description: 'Elige una de las siguientes opciones:', - chatWithConcierge: 'Chatear con Concierge', - scheduleSetupCall: 'Concertar una llamada', - questionMarkButtonTooltip: 'Obtén ayuda de nuestro equipo', - exploreHelpDocs: 'Explorar la documentación de ayuda', - }, - emojiPicker: { - skinTonePickerLabel: 'Elige el tono de piel por defecto', - headers: { - frequentlyUsed: 'Usado frecuentemente', - smileysAndEmotion: 'Emoticonos y emociones', - peopleAndBody: 'Personas y Cuerpo', - animalsAndNature: 'Animales y naturaleza', - foodAndDrink: 'Alimentos y bebidas', - travelAndPlaces: 'Viajes y lugares', - activities: 'Actividades', - objects: 'Objetos', - symbols: 'Símbolos', - flags: 'Banderas', - }, - }, - newRoomPage: { - newRoom: 'Nueva sala de chat', - roomName: 'Nombre de la sala', - visibility: 'Visibilidad', - restrictedDescription: 'Sólo las personas en tu espacio de trabajo pueden encontrar esta sala', - privateDescription: 'Sólo las personas que están invitadas a esta sala pueden encontrarla', - publicDescription: 'Cualquier persona puede unirse a esta sala', - public_announceDescription: 'Cualquier persona puede unirse a esta sala', - createRoom: 'Crea una sala de chat', - roomAlreadyExistsError: 'Ya existe una sala con este nombre', - roomNameReservedError: ({reservedName}) => `${reservedName} es el nombre una sala por defecto de todos los espacios de trabajo. Por favor, elige otro nombre.`, - roomNameInvalidError: 'Los nombres de las salas solo pueden contener minúsculas, números y guiones', - pleaseEnterRoomName: 'Por favor, escribe el nombre de una sala', - pleaseSelectWorkspace: 'Por favor, selecciona un espacio de trabajo', - renamedRoomAction: ({oldName, newName}) => ` cambió el nombre de la sala de ${oldName} a ${newName}`, - roomRenamedTo: ({newName}) => `Sala renombrada a ${newName}`, - social: 'social', - selectAWorkspace: 'Seleccionar un espacio de trabajo', - growlMessageOnRenameError: 'No se ha podido cambiar el nombre del espacio de trabajo, por favor, comprueba tu conexión e inténtalo de nuevo.', - visibilityOptions: { - restricted: 'Restringida', - private: 'Privada', - public: 'Público', - public_announce: 'Anuncio Público', - }, - }, - newTaskPage: { - assignTask: 'Asignar tarea', - assignMe: 'Asignar a mí mismo', - confirmTask: 'Confirmar tarea', - confirmError: 'Por favor, introduce un título y selecciona un destino de tarea.', - descriptionOptional: 'Descripción (opcional)', - shareSomewhere: 'Compartir en algún lugar', - pleaseEnterTaskName: 'Por favor, introduce un título', - pleaseEnterTaskDestination: 'Por favor, selecciona con quién deseas compartir.', - }, - task: { - task: 'Tarea', - title: 'Título', - description: 'Descripción', - assignee: 'Usuario asignado', - completed: 'Completada', + accountNumber: 'Número de cuenta', + routingNumber: 'Número de ruta', + addBankAccount: 'Añadir cuenta bancaria', + chooseAnAccount: 'Elige una cuenta', + connectOnlineWithPlaid: 'Conéctate a Plaid online', + connectManually: 'Conectar manualmente', + desktopConnection: 'Para conectarse con Chase, Wells Fargo, Capital One o Bank of America, haga clic aquí para completar este proceso en un navegador.', + yourDataIsSecure: 'Tus datos están seguros', + toGetStarted: 'Añade una cuenta bancaria y emite tarjetas corporativas, reembolsa gastos y cobra y paga facturas, todo desde un mismo lugar.', + plaidBodyCopy: 'Ofrezca a sus empleados una forma más sencilla de pagar - y recuperar - los gastos de la empresa.', + checkHelpLine: 'Su número de ruta y número de cuenta se pueden encontrar en un cheque de la cuenta bancaria.', + validateAccountError: + 'Para terminar de configurar tu cuenta bancaria, debes validar tu cuenta de Expensify. Por favor, revisa tu correo electrónico para validar tu cuenta y vuelve aquí para continuar.', + hasPhoneLoginError: + 'Para añadir una cuenta bancaria verificada, asegúrate de que tu nombre de usuario principal sea un correo electrónico válido y vuelve a intentarlo. Puedes añadir tu número de teléfono como nombre de usuario secundario.', + hasBeenThrottledError: 'Se produjo un error al intentar añadir tu cuenta bancaria. Por favor, espera unos minutos e inténtalo de nuevo.', + hasCurrencyError: + '¡Ups! Parece que la moneda de tu espacio de trabajo está configurada en una moneda diferente a USD. Para continuar, por favor configúrala en USD e inténtalo nuevamente.', + error: { + noBankAccountAvailable: 'Lo sentimos, no hay ninguna cuenta bancaria disponible', + noBankAccountSelected: 'Por favor, elige una cuenta bancaria', + taxID: 'Por favor, introduce un número de identificación fiscal válido', + website: 'Por favor, introduce un sitio web válido', + zipCode: 'Por favor, introduce un código postal válido', + phoneNumber: 'Por favor, introduce un teléfono válido', + companyName: 'Por favor, introduce un nombre comercial legal válido', + addressCity: 'Por favor, introduce una ciudad válida', + addressStreet: 'Por favor, introduce una calle de dirección válida que no sea un apartado postal', + addressState: 'Por favor, selecciona un estado', + incorporationDateFuture: 'La fecha de incorporación no puede ser futura', + incorporationState: 'Por favor, selecciona una estado válido', + industryCode: 'Por favor, introduce un código de clasificación de industria válido', + restrictedBusiness: 'Por favor, confirma que la empresa no está en la lista de negocios restringidos', + routingNumber: 'Por favor, introduce un número de ruta válido', + accountNumber: 'Por favor, introduce un número de cuenta válido', + routingAndAccountNumberCannotBeSame: 'El número de ruta y el número de cuenta no pueden ser iguales', + companyType: 'Por favor, selecciona un tipo de compañía válido', + tooManyAttempts: + 'Debido a la gran cantidad de intentos de inicio de sesión, esta opción se ha desactivado temporalmente durante 24 horas. Vuelve a intentarlo más tarde o introduce los detalles manualmente.', + address: 'Por favor, introduce una dirección válida', + dob: 'Por favor, selecciona una fecha de nacimiento válida', + age: 'Debe ser mayor de 18 años', + ssnLast4: 'Por favor, introduce los últimos 4 dígitos del número de seguridad social', + firstName: 'Por favor, introduce el nombre', + lastName: 'Por favor, introduce los apellidos', + noDefaultDepositAccountOrDebitCardAvailable: 'Por favor, añade una cuenta bancaria para depósitos o una tarjeta de débito', + validationAmounts: 'Los importes de validación que introduciste son incorrectos. Por favor, comprueba tu cuenta bancaria e inténtalo de nuevo.', + }, + }, + addPersonalBankAccountPage: { + enterPassword: 'Escribe tu contraseña de Expensify', + alreadyAdded: 'Esta cuenta ya ha sido añadida.', + chooseAccountLabel: 'Cuenta', + successTitle: '¡Cuenta bancaria personal añadida!', + successMessage: 'Enhorabuena, tu cuenta bancaria está lista para recibir reembolsos.', + }, + attachmentView: { + unknownFilename: 'Archivo desconocido', + passwordRequired: 'Por favor, introduce tu contraseña', + passwordIncorrect: 'Contraseña incorrecta. Por favor, inténtalo de nuevo.', + failedToLoadPDF: 'Hubo un error al intentar cargar el PDF.', + pdfPasswordForm: { + title: 'PDF protegido con contraseña', + infoText: 'Este PDF esta protegido con contraseña.', + beforeLinkText: 'Por favor', + linkText: 'introduce la contraseña', + afterLinkText: 'para verlo.', + formLabel: 'Ver PDF', + }, + }, messages: { - completed: 'tarea completada', - canceled: 'tarea cancelada', - reopened: 'tarea reabrir', - error: 'No tiene permiso para realizar la acción solicitada.', - }, - markAsDone: 'Marcar como completada', - markAsIncomplete: 'Marcar como incompleta', - assigneeError: 'Hubo un error al asignar esta tarea, inténtalo con otro usuario.', - }, - statementPage: { - generatingPDF: 'Estamos generando tu PDF ahora mismo. ¡Por favor, vuelve más tarde!', - }, - keyboardShortcutModal: { - title: 'Atajos de teclado', - subtitle: 'Ahorra tiempo con estos atajos de teclado:', - shortcuts: { - openShortcutDialog: 'Abre el cuadro de diálogo de métodos abreviados de teclado', - escape: 'Diálogos de escape', - search: 'Abrir diálogo de búsqueda', - newGroup: 'Nueva pantalla de grupo', - copy: 'Copiar comentario', - }, - }, - guides: { - screenShare: 'Compartir pantalla', - screenShareRequest: 'Expensify te está invitando a compartir la pantalla', - }, - genericErrorPage: { - title: '¡Uh-oh, algo salió mal!', - body: { - helpTextMobile: 'Intenta cerrar y volver a abrir la aplicación o cambiar a la', - helpTextWeb: 'web.', - helpTextConcierge: 'Si el problema persiste, comunícate con', - }, - refresh: 'Actualizar', - }, - fileDownload: { - success: { - title: '!Descargado!', - message: 'Archivo descargado correctamente', - }, - generalError: { - title: 'Error en la descarga', - message: 'No se puede descargar el archivo adjunto', - }, - permissionError: { - title: 'Permiso para acceder al almacenamiento', - message: 'Expensify no puede guardar los archivos adjuntos sin permiso para acceder al almacenamiento. Haz click en Configuración para actualizar los permisos.', - }, - }, - desktopApplicationMenu: { - mainMenu: 'Nuevo Expensify', - about: 'Sobre Nuevo Expensify', - update: 'Actualizar Nuevo Expensify', - checkForUpdates: 'Buscar actualizaciones', - toggleDevTools: 'Ver herramientas de desarrollo', - viewShortcuts: 'Ver atajos de teclado', - services: 'Servicios', - hide: 'Ocultar Nuevo Expensify', - hideOthers: 'Ocultar otros', - showAll: 'Mostrar todos', - quit: 'Salir de Nuevo Expensify', - fileMenu: 'Archivo', - closeWindow: 'Cerrar ventana', - editMenu: 'Editar', - undo: 'Deshacer', - redo: 'Rehacer', - cut: 'Cortar', - copy: 'Copiar', - paste: 'Pegar', - pasteAndMatchStyle: 'Pegar adaptando el estilo', - pasteAsPlainText: 'Pegar como texto sin formato', - delete: 'Eliminar', - selectAll: 'Seleccionar todo', - speechSubmenu: 'Voz', - startSpeaking: 'Empezar a hablar', - stopSpeaking: 'Dejar de Hablar', - viewMenu: 'Ver', - reload: 'Cargar de nuevo', - forceReload: 'Forzar recarga', - resetZoom: 'Tamaño real', - zoomIn: 'Acercar', - zoomOut: 'Alejar', - togglefullscreen: 'Alternar pantalla completa', - historyMenu: 'Historial', - back: 'Atrás', - forward: 'Adelante', - windowMenu: 'Ventana', - minimize: 'Minimizar', - zoom: 'Zoom', - front: 'Traer todo al frente', - helpMenu: 'Ayuda', - learnMore: 'Más información', - documentation: 'Documentación', - communityDiscussions: 'Debates de la comunidad', - searchIssues: 'Buscar problemas', - }, - historyMenu: { - forward: 'Adelante', - back: 'Atrás', - }, - checkForUpdatesModal: { - available: { - title: 'Actualización disponible', - message: 'La nueva versión estará disponible dentro de poco. Te notificaremos cuando esté lista.', - soundsGood: 'Suena bien', - }, - notAvailable: { - title: 'Actualización no disponible', - message: 'No existe ninguna actualización disponible! Inténtalo de nuevo más tarde.', - okay: 'Vale', + errorMessageInvalidPhone: `Por favor, introduce un número de teléfono válido sin paréntesis o guiones. Si reside fuera de Estados Unidos, por favor incluye el prefijo internacional (p. ej. ${CONST.EXAMPLE_PHONE_NUMBER}).`, + errorMessageInvalidEmail: 'Email inválido', + userIsAlreadyMemberOfWorkspace: ({login, workspace}) => `${login} ya es miembro de ${workspace}`, + }, + onfidoStep: { + acceptTerms: 'Al continuar con la solicitud para activar su billetera Expensify, confirma que ha leído, comprende y acepta ', + facialScan: 'Política y lanzamiento de la exploración facial de Onfido', + tryAgain: 'Intentar otra vez', + verifyIdentity: 'Verificar identidad', + genericError: 'Hubo un error al procesar este paso. Inténtalo de nuevo.', + cameraPermissionsNotGranted: 'No has habilitado los permisos para acceder a la cámara', + cameraRequestMessage: 'No has habilitado los permisos para acceder a la cámara. Necesitamos acceso para completar la verificaciôn.', + originalDocumentNeeded: 'Por favor, sube una imagen original de tu identificación en lugar de una captura de pantalla o imagen escaneada.', + documentNeedsBetterQuality: + 'Parece que tu identificación esta dañado o le faltan características de seguridad. Por favor, sube una imagen de tu documento sin daños y que se vea completamente.', + imageNeedsBetterQuality: 'Hay un problema con la calidad de la imagen de tu identificación. Por favor, sube una nueva imagen donde el identificación se vea con claridad.', + selfieIssue: 'Hay un problema con tu selfie/video. Por favor, sube un nuevo selfie/video grabado en el momento', + selfieNotMatching: 'Tu selfie/video no concuerda con tu identificación. Por favor, sube un nuevo selfie/video donde se vea tu cara con claridad.', + selfieNotLive: 'Tu selfie/video no parece ser un selfie/video en vivo. Por favor, sube un selfie/video a tiempo real.', + }, + additionalDetailsStep: { + headerTitle: 'Detalles adicionales', + helpText: 'Necesitamos confirmar la siguiente información antes de que podamos procesar el pago.', + helpTextIdologyQuestions: 'Tenemos que preguntarte unas preguntas más para terminar de verificar tu identidad', + helpLink: 'Obtén más información sobre por qué necesitamos esto.', + legalFirstNameLabel: 'Primer nombre legal', + legalMiddleNameLabel: 'Segundo nombre legal', + legalLastNameLabel: 'Apellidos legales', + selectAnswer: 'Selecciona una respuesta.', + ssnFull9Error: 'Por favor, introduce los 9 dígitos de un número de seguridad social válido', + needSSNFull9: 'Estamos teniendo problemas para verificar tu número de seguridad social. Introduce los 9 dígitos del número de seguridad social.', + weCouldNotVerify: 'No se pudo verificar', + pleaseFixIt: 'Corrige esta información antes de continuar.', + failedKYCTextBefore: 'No se ha podido verificar correctamente tu identidad. Vuelve a intentarlo más tarde y comunicate con ', + failedKYCTextAfter: ' si tienes alguna pregunta.', + }, + termsStep: { + headerTitle: 'Condiciones y tarifas', + haveReadAndAgree: 'He leído y acepto recibir ', + electronicDisclosures: 'divulgaciones electrónicas', + agreeToThe: 'Estoy de acuerdo con la ', + walletAgreement: 'Acuerdo de billetera', + enablePayments: 'Habilitar pagos', + feeAmountZero: '$0', + monthlyFee: 'Cuota mensual', + inactivity: 'Inactividad', + electronicFundsInstantFee: '1.5%', + noOverdraftOrCredit: 'Sin función de sobregiro / crédito', + electronicFundsWithdrawal: 'Retiro electrónico de fondos', + standard: 'Estándar', + shortTermsForm: { + expensifyPaymentsAccount: 'La billetera Expensify es emitida por The Bancorp Bank.', + perPurchase: 'Por compra', + atmWithdrawal: 'Retiro de cajero automático', + cashReload: 'Recarga de efectivo', + inNetwork: 'en la red', + outOfNetwork: 'fuera de la red', + atmBalanceInquiry: 'Consulta de saldo de cajero automático', + inOrOutOfNetwork: '(dentro o fuera de la red)', + customerService: 'Servicio al cliente', + automatedOrLive: '(agente automatizado o en vivo)', + afterTwelveMonths: '(después de 12 meses sin transacciones)', + weChargeOneFee: 'Cobramos un tipo de tarifa.', + fdicInsurance: 'Sus fondos son elegibles para el seguro de la FDIC.', + generalInfo: 'Para obtener información general sobre cuentas prepagas, visite', + conditionsDetails: 'Encuentra detalles y condiciones para todas las tarifas y servicios visitando', + conditionsPhone: 'o llamando al +1 833-400-0904.', + instant: '(instantáneo)', + electronicFundsInstantFeeMin: '(mínimo $0.25)', + }, + longTermsForm: { + listOfAllFees: 'Una lista de todas las tarifas de la billetera Expensify', + typeOfFeeHeader: 'Tipo de tarifa', + feeAmountHeader: 'Importe de la tarifa', + moreDetailsHeader: 'Más detalles', + openingAccountTitle: 'Abrir una cuenta', + openingAccountDetails: 'No hay tarifa para abrir una cuenta.', + monthlyFeeDetails: 'No hay tarifa mensual.', + customerServiceTitle: 'Servicio al cliente', + customerServiceDetails: 'No hay tarifas de servicio al cliente.', + inactivityDetails: 'No hay tarifa de inactividad.', + sendingFundsTitle: 'Enviar fondos a otro titular de cuenta', + sendingFundsDetails: 'No se aplica ningún cargo por enviar fondos a otro titular de cuenta utilizando su saldo cuenta bancaria o tarjeta de débito', + electronicFundsStandardDetails: + 'No hay cargo por transferir fondos desde su billetera Expensify ' + + 'a su cuenta bancaria utilizando la opción estándar. Esta transferencia generalmente se completa en' + + '1-3 negocios días.', + electronicFundsInstantDetails: + 'Hay una tarifa para transferir fondos desde su billetera Expensify a ' + + 'su tarjeta de débito vinculada utilizando la opción de transferencia instantánea. Esta transferencia ' + + 'generalmente se completa dentro de varios minutos. La tarifa es el 1.5% del monto de la ' + + 'transferencia (con una tarifa mínima de $ 0.25). ', + fdicInsuranceBancorp: + 'Sus fondos son elegibles para el seguro de la FDIC. Sus fondos se mantendrán en o ' + + 'transferido a The Bancorp Bank, una institución asegurada por la FDIC. Una vez allí, sus fondos ' + + 'están asegurados a $ 250,000 por la FDIC en caso de que The Bancorp Bank quiebre. Ver', + fdicInsuranceBancorp2: 'para detalles.', + contactExpensifyPayments: 'Comuníquese con Expensify Payments llamando al + 1833-400-0904, por correoelectrónico a', + contactExpensifyPayments2: 'o inicie sesión en', + generalInformation: 'Para obtener información general sobre cuentas prepagas, visite', + generalInformation2: 'Si tiene una queja sobre una cuenta prepaga, llame al Consumer Financial Oficina de Protección al 1-855-411-2372 o visite', + printerFriendlyView: 'Ver versión para imprimir', + automated: 'Automatizado', + liveAgent: 'Agente en vivo', + instant: 'Instantáneo', + electronicFundsInstantFeeMin: 'Mínimo $0.25', + }, }, - error: { - title: 'Comprobación fallida', - message: 'No hemos podido comprobar si existe una actualización. Inténtalo de nuevo más tarde!', - }, - }, - report: { - genericCreateReportFailureMessage: 'Error inesperado al crear el chat. Por favor, inténtalo más tarde', - genericAddCommentFailureMessage: 'Error inesperado al añadir el comentario. Por favor, inténtalo más tarde', - noActivityYet: 'Sin actividad todavía', - }, - chronos: { - oooEventSummaryFullDay: ({summary, dayCount, date}) => `${summary} por ${dayCount} ${dayCount === 1 ? 'día' : 'días'} hasta el ${date}`, - oooEventSummaryPartialDay: ({summary, timePeriod, date}) => `${summary} de ${timePeriod} del ${date}`, - }, - footer: { - features: 'Características', - expenseManagement: 'Gestión de Gastos', - spendManagement: 'Control de Gastos', - expenseReports: 'Informes de Gastos', - companyCreditCard: 'Tarjeta de Crédito Corporativa', - receiptScanningApp: 'Aplicación de Escaneado de Recibos', - billPay: 'Pago de Facturas', - invoicing: 'Facturación', - CPACard: 'Tarjeta Para Contables', - payroll: 'Nómina', - travel: 'Viajes', - resources: 'Recursos', - expensifyApproved: 'ExpensifyApproved!', - pressKit: 'Kit de Prensa', - support: 'Soporte', - expensifyHelp: 'ExpensifyHelp', - community: 'Comunidad', - privacy: 'Privacidad', - learnMore: 'Más Información', - aboutExpensify: 'Acerca de Expensify', - blog: 'Blog', - jobs: 'Empleo', - expensifyOrg: 'Expensify.org', - investorRelations: 'Relaciones Con Los Inversores', - getStarted: 'Comenzar', - createAccount: 'Crear Una Cuenta Nueva', - logIn: 'Conectarse', - }, - allStates: { - AK: { - stateISO: 'AK', - stateName: 'Alaska', - }, - AL: { - stateISO: 'AL', - stateName: 'Alabama', - }, - AR: { - stateISO: 'AR', - stateName: 'Arkansas', - }, - AZ: { - stateISO: 'AZ', - stateName: 'Arizona', - }, - CA: { - stateISO: 'CA', - stateName: 'California', - }, - CO: { - stateISO: 'CO', - stateName: 'Colorado', - }, - CT: { - stateISO: 'CT', - stateName: 'Connecticut', - }, - DE: { - stateISO: 'DE', - stateName: 'Delaware', - }, - FL: { - stateISO: 'FL', - stateName: 'Florida', - }, - GA: { - stateISO: 'GA', - stateName: 'Georgia', - }, - HI: { - stateISO: 'HI', - stateName: 'Hawái', - }, - IA: { - stateISO: 'IA', - stateName: 'Iowa', - }, - ID: { - stateISO: 'ID', - stateName: 'Idaho', - }, - IL: { - stateISO: 'IL', - stateName: 'Illinois', - }, - IN: { - stateISO: 'IN', - stateName: 'Indiana', - }, - KS: { - stateISO: 'KS', - stateName: 'Kansas', - }, - KY: { - stateISO: 'KY', - stateName: 'Kentucky', - }, - LA: { - stateISO: 'LA', - stateName: 'Luisiana', - }, - MA: { - stateISO: 'MA', - stateName: 'Massachusetts', - }, - MD: { - stateISO: 'MD', - stateName: 'Maryland', - }, - ME: { - stateISO: 'ME', - stateName: 'Maine', - }, - MI: { - stateISO: 'MI', - stateName: 'Míchigan', - }, - MN: { - stateISO: 'MN', - stateName: 'Minnesota', - }, - MO: { - stateISO: 'MO', - stateName: 'Misuri', + activateStep: { + headerTitle: 'Habilitar pagos', + activatedTitle: '¡Billetera activada!', + activatedMessage: 'Felicidades, tu Billetera está configurada y lista para hacer pagos.', + checkBackLaterTitle: 'Un momento...', + checkBackLaterMessage: 'Todavía estamos revisando tu información. Por favor, vuelva más tarde.', + continueToPayment: 'Continuar al pago', + continueToTransfer: 'Continuar a la transferencia', + }, + companyStep: { + headerTitle: 'Información de la empresa', + subtitle: '¡Ya casi estamos! Por motivos de seguridad, necesitamos confirmar la siguiente información:', + legalBusinessName: 'Nombre comercial legal', + companyWebsite: 'Página web de la empresa', + taxIDNumber: 'Número de identificación fiscal', + taxIDNumberPlaceholder: '9 dígitos', + companyType: 'Tipo de empresa', + incorporationDate: 'Fecha de incorporación', + incorporationState: 'Estado de incorporación', + industryClassificationCode: 'Código de clasificación industrial', + confirmCompanyIsNot: 'Confirmo que esta empresa no está en el', + listOfRestrictedBusinesses: 'lista de negocios restringidos', + incorporationDatePlaceholder: 'Fecha de inicio (aaaa-mm-dd)', + incorporationTypes: { + LLC: 'LLC', + CORPORATION: 'Corp', + PARTNERSHIP: 'Sociedad', + COOPERATIVE: 'Cooperativa', + SOLE_PROPRIETORSHIP: 'Propietario único', + OTHER: 'Otra', + }, }, - MS: { - stateISO: 'MS', - stateName: 'Misisipi', + requestorStep: { + headerTitle: 'Información personal', + subtitle: 'Dé más información sobre tí.', + learnMore: 'Más información', + isMyDataSafe: '¿Están seguros mis datos?', + onFidoConditions: 'Al continuar con la solicitud de añadir esta cuenta bancaria, confirma que ha leído, entiende y acepta ', + isControllingOfficer: 'Estoy autorizado a utilizar la cuenta bancaria de mi compañía para gastos de empresa', + isControllingOfficerError: 'Debe ser un oficial controlador con autorización para operar la cuenta bancaria de la compañía', + }, + validationStep: { + headerTitle: 'Validar cuenta bancaria', + buttonText: 'Finalizar configuración', + maxAttemptsReached: 'Se ha inhabilitado la validación de esta cuenta bancaria debido a demasiados intentos incorrectos.', + description: + 'Uno o dos días después de añadir tu cuenta a Expensify, te enviaremos tres (3) transacciones a tu cuenta. Tienen un nombre de comerciante similar a "Expensify, Inc. Validation".', + descriptionCTA: 'Introduce el importe de cada transacción en los campos siguientes. Ejemplo: 1.51.', + reviewingInfo: '¡Gracias! Estamos revisando tu información y nos comunicaremos contigo en breve. Consulta el chat con Concierge ', + forNextSteps: ' para conocer los próximos pasos para terminar de configurar tu cuenta bancaria.', + letsChatCTA: 'Sí, vamos a chatear', + letsChatText: 'Gracias. Necesitamos tu ayuda para verificar la información, pero podemos hacerlo rápidamente a través del chat. ¿Estás listo?', + letsChatTitle: '¡Vamos a chatear!', + enable2FATitle: 'Evita fraudes, activa la autenticación de dos factores!', + enable2FAText: + 'Tu seguridad es importante para nosotros. Por favor, configura ahora la autenticación de dos factores. Eso nos permitirá disputar las transacciones de la Tarjeta Expensify y reducirá tu riesgo de fraude.', + secureYourAccount: 'Asegura tu cuenta', + }, + beneficialOwnersStep: { + additionalInformation: 'Información adicional', + checkAllThatApply: 'Marca todos los que apliquen, en caso de que ninguno aplique dejar en blanco.', + iOwnMoreThan25Percent: 'Soy dueño de mas de 25% de ', + someoneOwnsMoreThan25Percent: 'Otra persona es dueña de mas de 25% de ', + additionalOwner: 'Beneficiario efectivo adicional', + removeOwner: 'Eliminar este beneficiario efectivo', + addAnotherIndividual: 'Añadir otra persona que es dueña de mas de 25% de ', + agreement: 'Acuerdo:', + termsAndConditions: 'Términos y condiciones', + certifyTrueAndAccurate: 'Certifico que la información dada es correcta', + error: { + certify: 'Debe certificar que la información es verdadera y precisa', + }, }, - MT: { - stateISO: 'MT', - stateName: 'Montana', + reimbursementAccountLoadingAnimation: { + oneMoment: 'Un momento', + explanationLine: 'Estamos verificando tu información y podrás continuar con los siguientes pasos en unos momentos.', + }, + session: { + offlineMessageRetry: 'Parece que estás desconectado. Por favor, comprueba tu conexión e inténtalo de nuevo.', + }, + workspace: { + common: { + card: 'Tarjetas', + workspace: 'Espacio de trabajo', + edit: 'Editar espacio de trabajo', + delete: 'Eliminar espacio de trabajo', + settings: 'Configuración', + reimburse: 'Reembolsos', + bills: 'Pagar facturas', + invoices: 'Enviar facturas', + travel: 'Viajes', + members: 'Miembros', + bankAccount: 'Cuenta bancaria', + connectBankAccount: 'Conectar cuenta bancaria', + testTransactions: 'Transacciones de prueba', + issueAndManageCards: 'Emitir y gestionar tarjetas', + reconcileCards: 'Reconciliar tarjetas', + settlementFrequency: 'Frecuencia de liquidación', + deleteConfirmation: '¿Estás seguro de que quieres eliminar este espacio de trabajo?', + unavailable: 'Espacio de trabajo no disponible', + memberNotFound: 'Miembro no encontrado. Para invitar a un nuevo miembro al espacio de trabajo, por favor, utiliza el botón Invitar que está arriba.', + notAuthorized: `No tienes acceso a esta página. ¿Estás tratando de unirte al espacio de trabajo? Comunícate con el propietario de este espacio de trabajo para que pueda añadirte como miembro. ¿Necesitas algo más? Comunícate con ${CONST.EMAIL.CONCIERGE}`, + goToRoom: ({roomName}) => `Ir a la sala ${roomName}`, + }, + emptyWorkspace: { + title: 'Crear un nuevo espacio de trabajo', + subtitle: 'En los espacios de trabajo es donde puedes chatear con tu equipo, reembolsar gastos, emitir tarjetas, enviar y pagar facturas y mas — todo en un mismo lugar', + features: { + trackAndCollect: 'Organiza recibos', + companyCards: 'Tarjetas de crédito corporativas', + reimbursements: 'Reembolsos fáciles', + }, + }, + new: { + newWorkspace: 'Nuevo espacio de trabajo', + getTheExpensifyCardAndMore: 'Consigue la Tarjeta Expensify y más', + }, + people: { + genericFailureMessage: 'Se ha producido un error al intentar eliminar a un usuario del espacio de trabajo. Por favor, inténtalo más tarde.', + removeMembersPrompt: '¿Estás seguro que quieres eliminar a los miembros seleccionados de tu espacio de trabajo?', + removeMembersTitle: 'Eliminar miembros', + selectAll: 'Seleccionar todo', + error: { + genericAdd: 'Ha ocurrido un problema al añadir el miembro al espacio de trabajo.', + cannotRemove: 'No puedes eliminarte ni a ti mismo ni al dueño del espacio de trabajo.', + genericRemove: 'Ha ocurrido un problema al eliminar al miembro del espacio de trabajo.', + }, + }, + card: { + header: 'Desbloquea Tarjetas Expensify gratis', + headerWithEcard: '¡Tus tarjetas están listas!', + noVBACopy: 'Conecta una cuenta bancaria para emitir tarjetas Expensify a los miembros de tu espacio de trabajo y accede a estos increíbles beneficios y más:', + VBANoECardCopy: + 'Añade tu correo electrónico de trabajo para emitir Tarjetas Expensify ilimitadas para los miembros de tu espacio de trabajo y acceder a todas estas increíbles ventajas:', + VBAWithECardCopy: 'Acceda a estos increíbles beneficios y más:', + benefit1: 'Hasta un 4% de devolución en tus gastos', + benefit2: 'Tarjetas digitales y físicas', + benefit3: 'Sin responsabilidad personal', + benefit4: 'Límites personalizables', + addWorkEmail: 'Añadir correo electrónico de trabajo', + checkingDomain: '¡Un momento! Estamos todavía trabajando para habilitar tu Tarjeta Expensify. Vuelve aquí en unos minutos.', + }, + reimburse: { + captureReceipts: 'Captura recibos', + fastReimbursementsHappyMembers: '¡Reembolsos rápidos = miembros felices!', + kilometers: 'Kilómetros', + miles: 'Millas', + viewAllReceipts: 'Ver todos los recibos', + reimburseReceipts: 'Reembolsar recibos', + trackDistance: 'Medir distancia', + trackDistanceCopy: 'Configura la tarifa y unidad usadas para medir distancias.', + trackDistanceRate: 'Tarifa', + trackDistanceUnit: 'Unidad', + unlockNextDayReimbursements: 'Desbloquea reembolsos diarios', + captureNoVBACopyBeforeEmail: 'Pide a los miembros de tu espacio de trabajo que envíen recibos a ', + captureNoVBACopyAfterEmail: ' y descarga la App de Expensify para controlar tus gastos en efectivo sobre la marcha.', + unlockNoVBACopy: 'Conecta una cuenta bancaria para reembolsar online a los miembros de tu espacio de trabajo.', + fastReimbursementsVBACopy: '¡Todo listo para reembolsar recibos desde tu cuenta bancaria!', + updateCustomUnitError: 'Los cambios no han podido ser guardados. El espacio de trabajo ha sido modificado mientras estabas desconectado. Por favor, inténtalo de nuevo.', + invalidRateError: 'Por favor, introduce una tarifa válida', + }, + bills: { + manageYourBills: 'Gestiona tus facturas', + askYourVendorsBeforeEmail: 'Pide a tus proveedores que envíen sus facturas a ', + askYourVendorsAfterEmail: ' y las escanearemos para que las pagues.', + viewAllBills: 'Ver facturas recibidas', + unlockOnlineBillPayment: 'Desbloquea el pago de facturas online', + unlockNoVBACopy: '¡Conecta tu cuenta bancaria para pagar tus facturas online de manera gratuita!', + hassleFreeBills: '¡Facturas sin complicaciones!', + VBACopy: '¡Todo listo para realizar pagos desde tu cuenta bancaria!', + }, + invoices: { + invoiceClientsAndCustomers: 'Emite facturas a tus clientes', + invoiceFirstSectionCopy: 'Envía facturas detalladas y profesionales directamente a tus clientes desde la app de Expensify.', + viewAllInvoices: 'Ver facturas emitidas', + unlockOnlineInvoiceCollection: 'Desbloquea el cobro de facturas online', + unlockNoVBACopy: 'Conecta tu cuenta bancaria para recibir pagos online de facturas - por transferencia o con tarjeta - directamente en tu cuenta.', + moneyBackInAFlash: '¡Tu dinero de vuelta en un momento!', + unlockVBACopy: '¡Todo listo para recibir pagos por transferencia o con tarjeta!', + viewUnpaidInvoices: 'Ver facturas emitidas pendientes', + sendInvoice: 'Enviar factura', + }, + travel: { + unlockConciergeBookingTravel: 'Desbloquea la reserva de viajes con Concierge', + noVBACopy: 'Conecta tu cuenta bancaria para permitir a los miembros de tu espacio de trabajo reservar sus vuelos, hoteles y coches empezando una conversación con Concierge.', + packYourBags: '¡Haz las maletas!', + VBACopy: '¡Miembros con la tarjeta Expensify pueden hablar con Concierge para reservar viajes!', + bookTravelWithConcierge: 'Reserva viajes con Concierge', + }, + invite: { + invitePeople: 'Invitar nuevos miembros', + genericFailureMessage: 'Se produjo un error al invitar al usuario al espacio de trabajo. Vuelva a intentarlo..', + pleaseEnterValidLogin: `Asegúrese de que el correo electrónico o el número de teléfono sean válidos (p. ej. ${CONST.EXAMPLE_PHONE_NUMBER}).`, + }, + inviteMessage: { + inviteMessageTitle: 'Añadir un mensaje', + inviteMessagePrompt: 'Añadir un mensaje para hacer tu invitación destacar', + personalMessagePrompt: 'Mensaje', + inviteNoMembersError: 'Por favor, selecciona al menos un miembro a invitar', + genericFailureMessage: 'Se produjo un error al invitar al usuario al espacio de trabajo. Vuelva a intentarlo..', + welcomeNote: ({workspaceName}) => + `¡Has sido invitado a ${workspaceName}! Descargue la aplicación móvil Expensify en use.expensify.com/download para comenzar a rastrear sus gastos.`, + }, + editor: { + nameInputLabel: 'Nombre', + nameInputHelpText: 'Este es el nombre que verás en tu espacio de trabajo.', + nameIsRequiredError: 'Debes definir un nombre para tu espacio de trabajo.', + nameIsTooLongError: `El nombre de su espacio de trabajo no puede tener más de ${CONST.WORKSPACE_NAME_CHARACTER_LIMIT} caracteres.`, + currencyInputLabel: 'Moneda por defecto', + currencyInputHelpText: 'Todas los gastos en este espacio de trabajo serán convertidos a esta moneda.', + currencyInputDisabledText: 'La moneda predeterminada no se puede cambiar porque este espacio de trabajo está vinculado a una cuenta bancaria en USD.', + save: 'Guardar', + genericFailureMessage: 'Se produjo un error al guardar el espacio de trabajo. Por favor, inténtalo de nuevo.', + avatarUploadFailureMessage: 'No se pudo subir el avatar. Por favor, inténtalo de nuevo.', + }, + bankAccount: { + continueWithSetup: 'Continuar con la configuración', + youreAlmostDone: + 'Casi has acabado de configurar tu cuenta bancaria, que te permitirá emitir tarjetas corporativas, reembolsar gastos y cobrar pagar facturas, todo desde la misma cuenta bancaria.', + streamlinePayments: 'Optimiza pagos', + oneMoreThing: '¡Una cosa más!', + allSet: '¡Todo listo!', + accountDescriptionNoCards: + 'Esta cuenta bancaria se utilizará para reembolsar gastos y cobrar y pagar facturas, todo desde la misma cuenta.\n\nPor favor, añade un correo electrónico de trabajo como tu nombre de usuario secundario para activar la Tarjeta Expensify.', + accountDescriptionWithCards: 'Esta cuenta bancaria se utilizará para emitir tarjetas corporativas, reembolsar gastos y cobrar y pagar facturas, todo desde la misma cuenta.', + addWorkEmail: 'Añadir correo electrónico de trabajo', + letsFinishInChat: '¡Continuemos en el chat!', + almostDone: '¡Casi listo!', + disconnectBankAccount: 'Desconectar cuenta bancaria', + noLetsStartOver: 'No, empecemos de nuevo', + startOver: 'Empezar de nuevo', + yesDisconnectMyBankAccount: 'Sí, desconecta mi cuenta bancaria', + yesStartOver: 'Sí, empezar de nuevo', + disconnectYour: 'Desconecta tu cuenta bancaria de ', + bankAccountAnyTransactions: '. Los reembolsos pendientes serán completados sin problemas.', + clearProgress: 'Empezar de nuevo descartará lo completado hasta ahora.', + areYouSure: '¿Estás seguro?', + workspaceCurrency: 'Moneda del espacio de trabajo', + updateCurrencyPrompt: + 'Parece que tu espacio de trabajo está configurado actualmente en una moneda diferente a USD. Por favor, haz clic en el botón de abajo para actualizar tu moneda a USD ahora.', + updateToUSD: 'Actualizar a USD', + }, }, - NC: { - stateISO: 'NC', - stateName: 'Carolina del Norte', + getAssistancePage: { + title: 'Obtener ayuda', + subtitle: '¡Estamos aquí para ayudarte!', + description: 'Elige una de las siguientes opciones:', + chatWithConcierge: 'Chatear con Concierge', + scheduleSetupCall: 'Concertar una llamada', + questionMarkButtonTooltip: 'Obtén ayuda de nuestro equipo', + exploreHelpDocs: 'Explorar la documentación de ayuda', + }, + emojiPicker: { + skinTonePickerLabel: 'Elige el tono de piel por defecto', + headers: { + frequentlyUsed: 'Usado frecuentemente', + smileysAndEmotion: 'Emoticonos y emociones', + peopleAndBody: 'Personas y Cuerpo', + animalsAndNature: 'Animales y naturaleza', + foodAndDrink: 'Alimentos y bebidas', + travelAndPlaces: 'Viajes y lugares', + activities: 'Actividades', + objects: 'Objetos', + symbols: 'Símbolos', + flags: 'Banderas', + }, }, - ND: { - stateISO: 'ND', - stateName: 'Dakota del Norte', + newRoomPage: { + newRoom: 'Nueva sala de chat', + roomName: 'Nombre de la sala', + visibility: 'Visibilidad', + restrictedDescription: 'Sólo las personas en tu espacio de trabajo pueden encontrar esta sala', + privateDescription: 'Sólo las personas que están invitadas a esta sala pueden encontrarla', + publicDescription: 'Cualquier persona puede unirse a esta sala', + public_announceDescription: 'Cualquier persona puede unirse a esta sala', + createRoom: 'Crea una sala de chat', + roomAlreadyExistsError: 'Ya existe una sala con este nombre', + roomNameReservedError: ({reservedName}) => `${reservedName} es el nombre una sala por defecto de todos los espacios de trabajo. Por favor, elige otro nombre.`, + roomNameInvalidError: 'Los nombres de las salas solo pueden contener minúsculas, números y guiones', + pleaseEnterRoomName: 'Por favor, escribe el nombre de una sala', + pleaseSelectWorkspace: 'Por favor, selecciona un espacio de trabajo', + renamedRoomAction: ({oldName, newName}) => ` cambió el nombre de la sala de ${oldName} a ${newName}`, + roomRenamedTo: ({newName}) => `Sala renombrada a ${newName}`, + social: 'social', + selectAWorkspace: 'Seleccionar un espacio de trabajo', + growlMessageOnRenameError: 'No se ha podido cambiar el nombre del espacio de trabajo, por favor, comprueba tu conexión e inténtalo de nuevo.', + visibilityOptions: { + restricted: 'Restringida', + private: 'Privada', + public: 'Público', + public_announce: 'Anuncio Público', + }, }, - NE: { - stateISO: 'NE', - stateName: 'Nebraska', + newTaskPage: { + assignTask: 'Asignar tarea', + assignMe: 'Asignar a mí mismo', + confirmTask: 'Confirmar tarea', + confirmError: 'Por favor, introduce un título y selecciona un destino de tarea.', + descriptionOptional: 'Descripción (opcional)', + shareSomewhere: 'Compartir en algún lugar', + pleaseEnterTaskName: 'Por favor, introduce un título', + pleaseEnterTaskDestination: 'Por favor, selecciona con quién deseas compartir.', + }, + task: { + task: 'Tarea', + title: 'Título', + description: 'Descripción', + assignee: 'Usuario asignado', + completed: 'Completada', + messages: { + completed: 'tarea completada', + canceled: 'tarea cancelada', + reopened: 'tarea reabrir', + error: 'No tiene permiso para realizar la acción solicitada.', + }, + markAsDone: 'Marcar como completada', + markAsIncomplete: 'Marcar como incompleta', + assigneeError: 'Hubo un error al asignar esta tarea, inténtalo con otro usuario.', + }, + statementPage: { + generatingPDF: 'Estamos generando tu PDF ahora mismo. ¡Por favor, vuelve más tarde!', + }, + keyboardShortcutModal: { + title: 'Atajos de teclado', + subtitle: 'Ahorra tiempo con estos atajos de teclado:', + shortcuts: { + openShortcutDialog: 'Abre el cuadro de diálogo de métodos abreviados de teclado', + escape: 'Diálogos de escape', + search: 'Abrir diálogo de búsqueda', + newGroup: 'Nueva pantalla de grupo', + copy: 'Copiar comentario', + }, }, - NH: { - stateISO: 'NH', - stateName: 'Nuevo Hampshire', - }, - NJ: { - stateISO: 'NJ', - stateName: 'Nueva Jersey', - }, - NM: { - stateISO: 'NM', - stateName: 'Nuevo México', - }, - NV: { - stateISO: 'NV', - stateName: 'Nevada', - }, - NY: { - stateISO: 'NY', - stateName: 'Nueva York', - }, - OH: { - stateISO: 'OH', - stateName: 'Ohio', - }, - OK: { - stateISO: 'OK', - stateName: 'Oklahoma', - }, - OR: { - stateISO: 'OR', - stateName: 'Oregón', - }, - PA: { - stateISO: 'PA', - stateName: 'Pensilvania', - }, - PR: { - stateISO: 'PR', - stateName: 'Puerto Rico', - }, - RI: { - stateISO: 'RI', - stateName: 'Rhode Island', - }, - SC: { - stateISO: 'SC', - stateName: 'Carolina del Sur', - }, - SD: { - stateISO: 'SD', - stateName: 'Dakota del Sur', - }, - TN: { - stateISO: 'TN', - stateName: 'Tennessee', - }, - TX: { - stateISO: 'TX', - stateName: 'Texas', - }, - UT: { - stateISO: 'UT', - stateName: 'Utah', - }, - VA: { - stateISO: 'VA', - stateName: 'Virginia', - }, - VT: { - stateISO: 'VT', - stateName: 'Vermont', - }, - WA: { - stateISO: 'WA', - stateName: 'Washington', - }, - WI: { - stateISO: 'WI', - stateName: 'Wisconsin', - }, - WV: { - stateISO: 'WV', - stateName: 'Virginia Occidental', - }, - WY: { - stateISO: 'WY', - stateName: 'Wyoming', - }, - DC: { - stateISO: 'DC', - stateName: 'Distrito de Columbia', - }, - }, - allCountries: { - AF: 'Afganistán', - AL: 'Albania', - DE: 'Alemania', - AD: 'Andorra', - AO: 'Angola', - AI: 'Anguila', - AQ: 'Antártida', - AG: 'Antigua y Barbuda', - SA: 'Arabia Saudita', - DZ: 'Argelia', - AR: 'Argentina', - AM: 'Armenia', - AW: 'Aruba', - AU: 'Australia', - AT: 'Austria', - AZ: 'Azerbaiyán', - BS: 'Bahamas', - BH: 'Bahrein', - BD: 'Bangladesh', - BB: 'Barbados', - BE: 'Bélgica', - BZ: 'Belice', - BJ: 'Benin', - BT: 'Bhután', - BY: 'Bielorrusia', - MM: 'Birmania', - BO: 'Bolivia', - BQ: 'Bonaire, San Eustaquio y Saba', - BA: 'Bosnia y Herzegovina', - BW: 'Botsuana', - BR: 'Brazil', - BN: 'Brunéi', - BG: 'Bulgaria', - BF: 'Burkina Faso', - BI: 'Burundi', - CV: 'Cabo Verde', - KH: 'Camboya', - CM: 'Camerún', - CA: 'Canadá', - TD: 'Chad', - CL: 'Chile', - CN: 'China', - CY: 'Chipre', - VA: 'Ciudad del Vaticano', - CO: 'Colombia', - KM: 'Comoras', - KP: 'Corea del Norte', - KR: 'Corea del Sur', - CI: 'Costa de Marfil', - CR: 'Costa Rica', - HR: 'Croacia', - CU: 'Cuba', - CW: 'Curazao', - DK: 'Dinamarca', - DM: 'Dominica', - EC: 'Ecuador', - EG: 'Egipto', - SV: 'El Salvador', - AE: 'Emiratos Árabes Unidos', - ER: 'Eritrea', - SK: 'Eslovaquia', - SI: 'Eslovenia', - ES: 'España', - US: 'Estados Unidos de América', - EE: 'Estonia', - ET: 'Etiopía', - PH: 'Filipinas', - FI: 'Finlandia', - FJ: 'Fiyi', - FR: 'Francia', - GA: 'Gabón', - GM: 'Gambia', - GE: 'Georgia', - GH: 'Ghana', - GI: 'Gibraltar', - GD: 'Granada', - GR: 'Greece', - GL: 'Groenlandia', - GP: 'Guadeloupe', - GU: 'Guam', - GT: 'Guatemala', - GF: 'Guayana Francesa', - GG: 'Guernsey', - GN: 'Guinea', - GQ: 'Guinea Ecuatorial', - GW: 'Guinea-Bissau', - GY: 'Guyana', - HT: 'Haiti', - HN: 'Honduras', - HK: 'Hong Kong', - HU: 'Hungría', - IN: 'India', - ID: 'Indonesia', - IQ: 'Irak', - IR: 'Irán', - IE: 'Irlanda', - AC: 'Isla Ascensión', - IM: 'Isla de Man', - CX: 'Isla de Navidad', - NF: 'Isla Norfolk', - IS: 'Islandia', - BM: 'Islas Bermudas', - KY: 'Islas Caimán', - CC: 'Islas Cocos (Keeling)', - CK: 'Islas Cook', - AX: 'Islas de Åland', - FO: 'Islas Feroe', - GS: 'Islas Georgias del Sur y Sandwich del Sur', - MV: 'Islas Maldivas', - FK: 'Islas Malvinas', - MP: 'Islas Marianas del Norte', - MH: 'Islas Marshall', - PN: 'Islas Pitcairn', - SB: 'Islas Salomón', - TC: 'Islas Turcas y Caicos', - UM: 'Islas Ultramarinas Menores de Estados Unidos', - VG: 'Islas Vírgenes Británicas', - VI: 'Islas Vírgenes de los Estados Unidos', - IL: 'Israel', - IT: 'Italia', - JM: 'Jamaica', - JP: 'Japón', - JE: 'Jersey', - JO: 'Jordania', - KZ: 'Kazajistán', - KE: 'Kenia', - KG: 'Kirguistán', - KI: 'Kiribati', - XK: 'Kosovo', - KW: 'Kuwait', - LA: 'Laos', - LS: 'Lesoto', - LV: 'Letonia', - LB: 'Líbano', - LR: 'Liberia', - LY: 'Libia', - LI: 'Liechtenstein', - LT: 'Lituania', - LU: 'Luxemburgo', - MO: 'Macao', - MK: 'Macedônia', - MG: 'Madagascar', - MY: 'Malasia', - MW: 'Malawi', - ML: 'Mali', - MT: 'Malta', - MA: 'Marruecos', - MQ: 'Martinica', - MR: 'Mauritania', - MU: 'Mauritius', - YT: 'Mayotte', - MX: 'México', - FM: 'Micronesia', - MD: 'Moldavia', - MC: 'Mónaco', - MN: 'Mongolia', - ME: 'Montenegro', - MS: 'Montserrat', - MZ: 'Mozambique', - NA: 'Namibia', - NR: 'Nauru', - NP: 'Nepal', - NI: 'Nicaragua', - NE: 'Niger', - NG: 'Nigeria', - NU: 'Niue', - NO: 'Noruega', - NC: 'Nueva Caledonia', - NZ: 'Nueva Zealand', - OM: 'Omán', - NL: 'Países Bajos', - PK: 'Pakistán', - PW: 'Palau', - PS: 'Palestina', - PA: 'Panamá', - PG: 'Papúa Nueva Guinea', - PY: 'Paraguay', - PE: 'Perú', - PF: 'Polinesia Francesa', - PL: 'Polonia', - PT: 'Portugal', - PR: 'Puerto Rico', - QA: 'Qatar', - GB: 'Reino Unido', - CF: 'República Centroafricana', - CZ: 'República Checa', - SS: 'República de Sudán del Sur', - CG: 'República del Congo', - CD: 'República Democrática del Congo', - DO: 'República Dominicana', - RE: 'Reunión', - RW: 'Ruanda', - RO: 'Rumanía', - RU: 'Rusia', - EH: 'Sahara Occidental', - WS: 'Samoa', - AS: 'Samoa Americana', - BL: 'San Bartolomé', - KN: 'San Cristóbal y Nieves', - SM: 'San Marino', - MF: 'San Martín (Francia)', - PM: 'San Pedro y Miquelón', - VC: 'San Vicente y las Granadinas', - SH: 'Santa Elena', - LC: 'Santa Lucía', - ST: 'Santo Tomé y Príncipe', - SN: 'Senegal', - RS: 'Serbia', - SC: 'Seychelles', - SL: 'Sierra Leona', - SG: 'Singapur', - SX: 'Sint Maarten', - SY: 'Siria', - SO: 'Somalia', - LK: 'Sri Lanka', - ZA: 'Sudáfrica', - SD: 'Sudán', - SE: 'Suecia', - CH: 'Suiza', - SR: 'Surinám', - SJ: 'Svalbard y Jan Mayen', - SZ: 'Swazilandia', - TH: 'Tailandia', - TW: 'Taiwán', - TZ: 'Tanzania', - TJ: 'Tayikistán', - IO: 'Territorio Británico del Océano Índico', - TF: 'Territorios Australes y Antárticas Franceses', - TL: 'Timor Oriental', - TG: 'Togo', - TK: 'Tokelau', - TO: 'Tonga', - TT: 'Trinidad y Tobago', - TA: 'Tristán de Acuña', - TN: 'Tunez', - TM: 'Turkmenistán', - TR: 'Turquía', - TV: 'Tuvalu', - UA: 'Ucrania', - UG: 'Uganda', - UY: 'Uruguay', - UZ: 'Uzbekistan', - VU: 'Vanuatu', - VE: 'Venezuela', - VN: 'Vietnam', - WF: 'Wallis y Futuna', - YE: 'Yemen', - DJ: 'Yibuti', - ZM: 'Zambia', - ZW: 'Zimbabue', - }, - accessibilityHints: { - navigateToChatsList: 'Vuelve a la lista de chats', - chatWelcomeMessage: 'Mensaje de bienvenida al chat', - navigatesToChat: 'Navega a un chat', - newMessageLineIndicator: 'Indicador de nueva línea de mensaje', - chatMessage: 'mensaje de chat', - lastChatMessagePreview: 'Vista previa del último mensaje del chat', - workspaceName: 'Nombre del espacio de trabajo', - chatUserDisplayNames: 'Nombres de los usuarios del chat', - scrollToNewestMessages: 'Desplázate a los mensajes más recientes', - prestyledText: 'texto preestilizado', - viewAttachment: 'Ver archivo adjunto', - }, - parentReportAction: { - deletedMessage: '[Mensaje eliminado]', - deletedRequest: '[Pedido eliminado]', - hiddenMessage: '[Mensaje oculto]', - }, - threads: { - replies: 'Respuestas', - reply: 'Respuesta', - from: 'De', - in: 'en', - parentNavigationSummary: ({rootReportName, workspaceName}) => `De ${rootReportName}${workspaceName ? ` en ${workspaceName}` : ''}`, - }, - qrCodes: { - copyUrlToClipboard: 'Copiar URL al portapapeles', - copied: '¡Copiado!', - }, - moderation: { - flagDescription: 'Todos los mensajes marcados se enviarán a un moderador para su revisión.', - chooseAReason: 'Elige abajo un motivo para reportarlo:', - spam: 'Spam', - spamDescription: 'Promoción fuera de tema no solicitada', - inconsiderate: 'Desconsiderado', - inconsiderateDescription: 'Frase insultante o irrespetuosa, con intenciones cuestionables', - intimidation: 'Intimidación', - intimidationDescription: 'Persigue agresivamente una agenda sobre objeciones válidas', - bullying: 'Bullying', - bullyingDescription: 'Apunta a un individuo para obtener obediencia', - harassment: 'Acoso', - harassmentDescription: 'Comportamiento racista, misógino u otro comportamiento discriminatorio', - assault: 'Agresion', - assaultDescription: 'Ataque emocional específicamente dirigido con la intención de hacer daño', - flaggedContent: 'Este mensaje ha sido marcado por violar las reglas de nuestra comunidad y el contenido se ha ocultado.', - hideMessage: 'Ocultar mensaje', - revealMessage: 'Revelar mensaje', - levelOneResult: 'Envia una advertencia anónima y el mensaje es reportado para revisión.', - levelTwoResult: 'Mensaje ocultado del canal, más advertencia anónima y mensaje reportado para revisión.', - levelThreeResult: 'Mensaje eliminado del canal, más advertencia anónima y mensaje reportado para revisión.', - }, - distance: { - addStop: 'Agregar parada', - address: 'Dirección', - waypointEditor: 'Editor de puntos de ruta', - waypointDescription: { - start: 'Comienzo', - finish: 'Final', - stop: 'Parada', - }, - mapPending: { - title: 'Mapa pendiente', - subtitle: 'El mapa se generará cuando vuelvas a estar en línea', - onlineSubtitle: 'Un momento mientras configuramos el mapa', + guides: { + screenShare: 'Compartir pantalla', + screenShareRequest: 'Expensify te está invitando a compartir la pantalla', }, - errors: { - selectSuggestedAddress: 'Por favor, selecciona una dirección sugerida', + genericErrorPage: { + title: '¡Uh-oh, algo salió mal!', + body: { + helpTextMobile: 'Intenta cerrar y volver a abrir la aplicación o cambiar a la', + helpTextWeb: 'web.', + helpTextConcierge: 'Si el problema persiste, comunícate con', + }, + refresh: 'Actualizar', + }, + fileDownload: { + success: { + title: '!Descargado!', + message: 'Archivo descargado correctamente', + }, + generalError: { + title: 'Error en la descarga', + message: 'No se puede descargar el archivo adjunto', + }, + permissionError: { + title: 'Permiso para acceder al almacenamiento', + message: 'Expensify no puede guardar los archivos adjuntos sin permiso para acceder al almacenamiento. Haz click en Configuración para actualizar los permisos.', + }, + }, + desktopApplicationMenu: { + mainMenu: 'Nuevo Expensify', + about: 'Sobre Nuevo Expensify', + update: 'Actualizar Nuevo Expensify', + checkForUpdates: 'Buscar actualizaciones', + toggleDevTools: 'Ver herramientas de desarrollo', + viewShortcuts: 'Ver atajos de teclado', + services: 'Servicios', + hide: 'Ocultar Nuevo Expensify', + hideOthers: 'Ocultar otros', + showAll: 'Mostrar todos', + quit: 'Salir de Nuevo Expensify', + fileMenu: 'Archivo', + closeWindow: 'Cerrar ventana', + editMenu: 'Editar', + undo: 'Deshacer', + redo: 'Rehacer', + cut: 'Cortar', + copy: 'Copiar', + paste: 'Pegar', + pasteAndMatchStyle: 'Pegar adaptando el estilo', + pasteAsPlainText: 'Pegar como texto sin formato', + delete: 'Eliminar', + selectAll: 'Seleccionar todo', + speechSubmenu: 'Voz', + startSpeaking: 'Empezar a hablar', + stopSpeaking: 'Dejar de Hablar', + viewMenu: 'Ver', + reload: 'Cargar de nuevo', + forceReload: 'Forzar recarga', + resetZoom: 'Tamaño real', + zoomIn: 'Acercar', + zoomOut: 'Alejar', + togglefullscreen: 'Alternar pantalla completa', + historyMenu: 'Historial', + back: 'Atrás', + forward: 'Adelante', + windowMenu: 'Ventana', + minimize: 'Minimizar', + zoom: 'Zoom', + front: 'Traer todo al frente', + helpMenu: 'Ayuda', + learnMore: 'Más información', + documentation: 'Documentación', + communityDiscussions: 'Debates de la comunidad', + searchIssues: 'Buscar problemas', + }, + historyMenu: { + forward: 'Adelante', + back: 'Atrás', + }, + checkForUpdatesModal: { + available: { + title: 'Actualización disponible', + message: 'La nueva versión estará disponible dentro de poco. Te notificaremos cuando esté lista.', + soundsGood: 'Suena bien', + }, + notAvailable: { + title: 'Actualización no disponible', + message: 'No existe ninguna actualización disponible! Inténtalo de nuevo más tarde.', + okay: 'Vale', + }, + error: { + title: 'Comprobación fallida', + message: 'No hemos podido comprobar si existe una actualización. Inténtalo de nuevo más tarde!', + }, + }, + report: { + genericCreateReportFailureMessage: 'Error inesperado al crear el chat. Por favor, inténtalo más tarde', + genericAddCommentFailureMessage: 'Error inesperado al añadir el comentario. Por favor, inténtalo más tarde', + noActivityYet: 'Sin actividad todavía', + }, + chronos: { + oooEventSummaryFullDay: ({summary, dayCount, date}) => `${summary} por ${dayCount} ${dayCount === 1 ? 'día' : 'días'} hasta el ${date}`, + oooEventSummaryPartialDay: ({summary, timePeriod, date}) => `${summary} de ${timePeriod} del ${date}`, + }, + footer: { + features: 'Características', + expenseManagement: 'Gestión de Gastos', + spendManagement: 'Control de Gastos', + expenseReports: 'Informes de Gastos', + companyCreditCard: 'Tarjeta de Crédito Corporativa', + receiptScanningApp: 'Aplicación de Escaneado de Recibos', + billPay: 'Pago de Facturas', + invoicing: 'Facturación', + CPACard: 'Tarjeta Para Contables', + payroll: 'Nómina', + travel: 'Viajes', + resources: 'Recursos', + expensifyApproved: 'ExpensifyApproved!', + pressKit: 'Kit de Prensa', + support: 'Soporte', + expensifyHelp: 'ExpensifyHelp', + community: 'Comunidad', + privacy: 'Privacidad', + learnMore: 'Más Información', + aboutExpensify: 'Acerca de Expensify', + blog: 'Blog', + jobs: 'Empleo', + expensifyOrg: 'Expensify.org', + investorRelations: 'Relaciones Con Los Inversores', + getStarted: 'Comenzar', + createAccount: 'Crear Una Cuenta Nueva', + logIn: 'Conectarse', + }, + allStates: { + AK: { + stateISO: 'AK', + stateName: 'Alaska', + }, + AL: { + stateISO: 'AL', + stateName: 'Alabama', + }, + AR: { + stateISO: 'AR', + stateName: 'Arkansas', + }, + AZ: { + stateISO: 'AZ', + stateName: 'Arizona', + }, + CA: { + stateISO: 'CA', + stateName: 'California', + }, + CO: { + stateISO: 'CO', + stateName: 'Colorado', + }, + CT: { + stateISO: 'CT', + stateName: 'Connecticut', + }, + DE: { + stateISO: 'DE', + stateName: 'Delaware', + }, + FL: { + stateISO: 'FL', + stateName: 'Florida', + }, + GA: { + stateISO: 'GA', + stateName: 'Georgia', + }, + HI: { + stateISO: 'HI', + stateName: 'Hawái', + }, + IA: { + stateISO: 'IA', + stateName: 'Iowa', + }, + ID: { + stateISO: 'ID', + stateName: 'Idaho', + }, + IL: { + stateISO: 'IL', + stateName: 'Illinois', + }, + IN: { + stateISO: 'IN', + stateName: 'Indiana', + }, + KS: { + stateISO: 'KS', + stateName: 'Kansas', + }, + KY: { + stateISO: 'KY', + stateName: 'Kentucky', + }, + LA: { + stateISO: 'LA', + stateName: 'Luisiana', + }, + MA: { + stateISO: 'MA', + stateName: 'Massachusetts', + }, + MD: { + stateISO: 'MD', + stateName: 'Maryland', + }, + ME: { + stateISO: 'ME', + stateName: 'Maine', + }, + MI: { + stateISO: 'MI', + stateName: 'Míchigan', + }, + MN: { + stateISO: 'MN', + stateName: 'Minnesota', + }, + MO: { + stateISO: 'MO', + stateName: 'Misuri', + }, + MS: { + stateISO: 'MS', + stateName: 'Misisipi', + }, + MT: { + stateISO: 'MT', + stateName: 'Montana', + }, + NC: { + stateISO: 'NC', + stateName: 'Carolina del Norte', + }, + ND: { + stateISO: 'ND', + stateName: 'Dakota del Norte', + }, + NE: { + stateISO: 'NE', + stateName: 'Nebraska', + }, + NH: { + stateISO: 'NH', + stateName: 'Nuevo Hampshire', + }, + NJ: { + stateISO: 'NJ', + stateName: 'Nueva Jersey', + }, + NM: { + stateISO: 'NM', + stateName: 'Nuevo México', + }, + NV: { + stateISO: 'NV', + stateName: 'Nevada', + }, + NY: { + stateISO: 'NY', + stateName: 'Nueva York', + }, + OH: { + stateISO: 'OH', + stateName: 'Ohio', + }, + OK: { + stateISO: 'OK', + stateName: 'Oklahoma', + }, + OR: { + stateISO: 'OR', + stateName: 'Oregón', + }, + PA: { + stateISO: 'PA', + stateName: 'Pensilvania', + }, + PR: { + stateISO: 'PR', + stateName: 'Puerto Rico', + }, + RI: { + stateISO: 'RI', + stateName: 'Rhode Island', + }, + SC: { + stateISO: 'SC', + stateName: 'Carolina del Sur', + }, + SD: { + stateISO: 'SD', + stateName: 'Dakota del Sur', + }, + TN: { + stateISO: 'TN', + stateName: 'Tennessee', + }, + TX: { + stateISO: 'TX', + stateName: 'Texas', + }, + UT: { + stateISO: 'UT', + stateName: 'Utah', + }, + VA: { + stateISO: 'VA', + stateName: 'Virginia', + }, + VT: { + stateISO: 'VT', + stateName: 'Vermont', + }, + WA: { + stateISO: 'WA', + stateName: 'Washington', + }, + WI: { + stateISO: 'WI', + stateName: 'Wisconsin', + }, + WV: { + stateISO: 'WV', + stateName: 'Virginia Occidental', + }, + WY: { + stateISO: 'WY', + stateName: 'Wyoming', + }, + DC: { + stateISO: 'DC', + stateName: 'Distrito de Columbia', + }, + }, + allCountries: { + AF: 'Afganistán', + AL: 'Albania', + DE: 'Alemania', + AD: 'Andorra', + AO: 'Angola', + AI: 'Anguila', + AQ: 'Antártida', + AG: 'Antigua y Barbuda', + SA: 'Arabia Saudita', + DZ: 'Argelia', + AR: 'Argentina', + AM: 'Armenia', + AW: 'Aruba', + AU: 'Australia', + AT: 'Austria', + AZ: 'Azerbaiyán', + BS: 'Bahamas', + BH: 'Bahrein', + BD: 'Bangladesh', + BB: 'Barbados', + BE: 'Bélgica', + BZ: 'Belice', + BJ: 'Benin', + BT: 'Bhután', + BY: 'Bielorrusia', + MM: 'Birmania', + BO: 'Bolivia', + BQ: 'Bonaire, San Eustaquio y Saba', + BA: 'Bosnia y Herzegovina', + BW: 'Botsuana', + BR: 'Brazil', + BN: 'Brunéi', + BG: 'Bulgaria', + BF: 'Burkina Faso', + BI: 'Burundi', + CV: 'Cabo Verde', + KH: 'Camboya', + CM: 'Camerún', + CA: 'Canadá', + TD: 'Chad', + CL: 'Chile', + CN: 'China', + CY: 'Chipre', + VA: 'Ciudad del Vaticano', + CO: 'Colombia', + KM: 'Comoras', + KP: 'Corea del Norte', + KR: 'Corea del Sur', + CI: 'Costa de Marfil', + CR: 'Costa Rica', + HR: 'Croacia', + CU: 'Cuba', + CW: 'Curazao', + DK: 'Dinamarca', + DM: 'Dominica', + EC: 'Ecuador', + EG: 'Egipto', + SV: 'El Salvador', + AE: 'Emiratos Árabes Unidos', + ER: 'Eritrea', + SK: 'Eslovaquia', + SI: 'Eslovenia', + ES: 'España', + US: 'Estados Unidos de América', + EE: 'Estonia', + ET: 'Etiopía', + PH: 'Filipinas', + FI: 'Finlandia', + FJ: 'Fiyi', + FR: 'Francia', + GA: 'Gabón', + GM: 'Gambia', + GE: 'Georgia', + GH: 'Ghana', + GI: 'Gibraltar', + GD: 'Granada', + GR: 'Greece', + GL: 'Groenlandia', + GP: 'Guadeloupe', + GU: 'Guam', + GT: 'Guatemala', + GF: 'Guayana Francesa', + GG: 'Guernsey', + GN: 'Guinea', + GQ: 'Guinea Ecuatorial', + GW: 'Guinea-Bissau', + GY: 'Guyana', + HT: 'Haiti', + HN: 'Honduras', + HK: 'Hong Kong', + HU: 'Hungría', + IN: 'India', + ID: 'Indonesia', + IQ: 'Irak', + IR: 'Irán', + IE: 'Irlanda', + AC: 'Isla Ascensión', + IM: 'Isla de Man', + CX: 'Isla de Navidad', + NF: 'Isla Norfolk', + IS: 'Islandia', + BM: 'Islas Bermudas', + KY: 'Islas Caimán', + CC: 'Islas Cocos (Keeling)', + CK: 'Islas Cook', + AX: 'Islas de Åland', + FO: 'Islas Feroe', + GS: 'Islas Georgias del Sur y Sandwich del Sur', + MV: 'Islas Maldivas', + FK: 'Islas Malvinas', + MP: 'Islas Marianas del Norte', + MH: 'Islas Marshall', + PN: 'Islas Pitcairn', + SB: 'Islas Salomón', + TC: 'Islas Turcas y Caicos', + UM: 'Islas Ultramarinas Menores de Estados Unidos', + VG: 'Islas Vírgenes Británicas', + VI: 'Islas Vírgenes de los Estados Unidos', + IL: 'Israel', + IT: 'Italia', + JM: 'Jamaica', + JP: 'Japón', + JE: 'Jersey', + JO: 'Jordania', + KZ: 'Kazajistán', + KE: 'Kenia', + KG: 'Kirguistán', + KI: 'Kiribati', + XK: 'Kosovo', + KW: 'Kuwait', + LA: 'Laos', + LS: 'Lesoto', + LV: 'Letonia', + LB: 'Líbano', + LR: 'Liberia', + LY: 'Libia', + LI: 'Liechtenstein', + LT: 'Lituania', + LU: 'Luxemburgo', + MO: 'Macao', + MK: 'Macedônia', + MG: 'Madagascar', + MY: 'Malasia', + MW: 'Malawi', + ML: 'Mali', + MT: 'Malta', + MA: 'Marruecos', + MQ: 'Martinica', + MR: 'Mauritania', + MU: 'Mauritius', + YT: 'Mayotte', + MX: 'México', + FM: 'Micronesia', + MD: 'Moldavia', + MC: 'Mónaco', + MN: 'Mongolia', + ME: 'Montenegro', + MS: 'Montserrat', + MZ: 'Mozambique', + NA: 'Namibia', + NR: 'Nauru', + NP: 'Nepal', + NI: 'Nicaragua', + NE: 'Niger', + NG: 'Nigeria', + NU: 'Niue', + NO: 'Noruega', + NC: 'Nueva Caledonia', + NZ: 'Nueva Zealand', + OM: 'Omán', + NL: 'Países Bajos', + PK: 'Pakistán', + PW: 'Palau', + PS: 'Palestina', + PA: 'Panamá', + PG: 'Papúa Nueva Guinea', + PY: 'Paraguay', + PE: 'Perú', + PF: 'Polinesia Francesa', + PL: 'Polonia', + PT: 'Portugal', + PR: 'Puerto Rico', + QA: 'Qatar', + GB: 'Reino Unido', + CF: 'República Centroafricana', + CZ: 'República Checa', + SS: 'República de Sudán del Sur', + CG: 'República del Congo', + CD: 'República Democrática del Congo', + DO: 'República Dominicana', + RE: 'Reunión', + RW: 'Ruanda', + RO: 'Rumanía', + RU: 'Rusia', + EH: 'Sahara Occidental', + WS: 'Samoa', + AS: 'Samoa Americana', + BL: 'San Bartolomé', + KN: 'San Cristóbal y Nieves', + SM: 'San Marino', + MF: 'San Martín (Francia)', + PM: 'San Pedro y Miquelón', + VC: 'San Vicente y las Granadinas', + SH: 'Santa Elena', + LC: 'Santa Lucía', + ST: 'Santo Tomé y Príncipe', + SN: 'Senegal', + RS: 'Serbia', + SC: 'Seychelles', + SL: 'Sierra Leona', + SG: 'Singapur', + SX: 'Sint Maarten', + SY: 'Siria', + SO: 'Somalia', + LK: 'Sri Lanka', + ZA: 'Sudáfrica', + SD: 'Sudán', + SE: 'Suecia', + CH: 'Suiza', + SR: 'Surinám', + SJ: 'Svalbard y Jan Mayen', + SZ: 'Swazilandia', + TH: 'Tailandia', + TW: 'Taiwán', + TZ: 'Tanzania', + TJ: 'Tayikistán', + IO: 'Territorio Británico del Océano Índico', + TF: 'Territorios Australes y Antárticas Franceses', + TL: 'Timor Oriental', + TG: 'Togo', + TK: 'Tokelau', + TO: 'Tonga', + TT: 'Trinidad y Tobago', + TA: 'Tristán de Acuña', + TN: 'Tunez', + TM: 'Turkmenistán', + TR: 'Turquía', + TV: 'Tuvalu', + UA: 'Ucrania', + UG: 'Uganda', + UY: 'Uruguay', + UZ: 'Uzbekistan', + VU: 'Vanuatu', + VE: 'Venezuela', + VN: 'Vietnam', + WF: 'Wallis y Futuna', + YE: 'Yemen', + DJ: 'Yibuti', + ZM: 'Zambia', + ZW: 'Zimbabue', + }, + accessibilityHints: { + navigateToChatsList: 'Vuelve a la lista de chats', + chatWelcomeMessage: 'Mensaje de bienvenida al chat', + navigatesToChat: 'Navega a un chat', + newMessageLineIndicator: 'Indicador de nueva línea de mensaje', + chatMessage: 'mensaje de chat', + lastChatMessagePreview: 'Vista previa del último mensaje del chat', + workspaceName: 'Nombre del espacio de trabajo', + chatUserDisplayNames: 'Nombres de los usuarios del chat', + scrollToNewestMessages: 'Desplázate a los mensajes más recientes', + prestyledText: 'texto preestilizado', + viewAttachment: 'Ver archivo adjunto', + }, + parentReportAction: { + deletedMessage: '[Mensaje eliminado]', + deletedRequest: '[Pedido eliminado]', + hiddenMessage: '[Mensaje oculto]', + }, + threads: { + replies: 'Respuestas', + reply: 'Respuesta', + from: 'De', + in: 'en', + parentNavigationSummary: ({rootReportName, workspaceName}) => `De ${rootReportName}${workspaceName ? ` en ${workspaceName}` : ''}`, + }, + qrCodes: { + copyUrlToClipboard: 'Copiar URL al portapapeles', + copied: '¡Copiado!', + }, + moderation: { + flagDescription: 'Todos los mensajes marcados se enviarán a un moderador para su revisión.', + chooseAReason: 'Elige abajo un motivo para reportarlo:', + spam: 'Spam', + spamDescription: 'Promoción fuera de tema no solicitada', + inconsiderate: 'Desconsiderado', + inconsiderateDescription: 'Frase insultante o irrespetuosa, con intenciones cuestionables', + intimidation: 'Intimidación', + intimidationDescription: 'Persigue agresivamente una agenda sobre objeciones válidas', + bullying: 'Bullying', + bullyingDescription: 'Apunta a un individuo para obtener obediencia', + harassment: 'Acoso', + harassmentDescription: 'Comportamiento racista, misógino u otro comportamiento discriminatorio', + assault: 'Agresion', + assaultDescription: 'Ataque emocional específicamente dirigido con la intención de hacer daño', + flaggedContent: 'Este mensaje ha sido marcado por violar las reglas de nuestra comunidad y el contenido se ha ocultado.', + hideMessage: 'Ocultar mensaje', + revealMessage: 'Revelar mensaje', + levelOneResult: 'Envia una advertencia anónima y el mensaje es reportado para revisión.', + levelTwoResult: 'Mensaje ocultado del canal, más advertencia anónima y mensaje reportado para revisión.', + levelThreeResult: 'Mensaje eliminado del canal, más advertencia anónima y mensaje reportado para revisión.', + }, + distance: { + addStop: 'Agregar parada', + address: 'Dirección', + waypointEditor: 'Editor de puntos de ruta', + waypointDescription: { + start: 'Comienzo', + finish: 'Final', + stop: 'Parada', + }, + mapPending: { + title: 'Mapa pendiente', + subtitle: 'El mapa se generará cuando vuelvas a estar en línea', + onlineSubtitle: 'Un momento mientras configuramos el mapa', + }, + errors: { + selectSuggestedAddress: 'Por favor, selecciona una dirección sugerida', + }, + }, + countrySelectorModal: { + placeholderText: 'Buscar para ver opciones', + }, + stateSelectorModal: { + placeholderText: 'Buscar para ver opciones', }, - }, - countrySelectorModal: { - placeholderText: 'Buscar para ver opciones', - }, - stateSelectorModal: { - placeholderText: 'Buscar para ver opciones', }, }; diff --git a/src/pages/settings/Profile/CustomStatus/StatusPage.js b/src/pages/settings/Profile/CustomStatus/StatusPage.js index e708b53708b1..28a2332db221 100644 --- a/src/pages/settings/Profile/CustomStatus/StatusPage.js +++ b/src/pages/settings/Profile/CustomStatus/StatusPage.js @@ -37,11 +37,10 @@ function StatusPage({draftStatus, currentUserPersonalDetails}) { const defaultEmoji = draftEmojiCode || currentUserEmojiCode; const defaultText = draftEmojiCode ? draftText : currentUserStatusText; const customStatus = draftEmojiCode ? `${draftEmojiCode} ${draftText}` : `${currentUserEmojiCode || ''} ${currentUserStatusText || ''}`; - const hasDraftStatus = !!draftEmojiCode || !!draftText; const formattedClearAfter = DateUtils.formatDateTo12Hour(lodashGet(currentUserPersonalDetails, 'status.clearAfter', '')); const formattedClearAfterDraft = DateUtils.formatDateTo12Hour(draftClearAfter); - const customClearAfter = draftClearAfter ? formattedClearAfterDraft : formattedClearAfter ||''; + const customClearAfter = draftClearAfter ? formattedClearAfterDraft : formattedClearAfter || ''; const updateStatus = useCallback(() => { const endOfDay = moment().endOf('day').format('YYYY-MM-DD HH:mm:ss'); From 243c7e3d40c461ca56e289af361de80082da8fd2 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Tue, 22 Aug 2023 10:40:25 +0200 Subject: [PATCH 014/615] add SetTimePage --- src/languages/en.js | 8 +- src/languages/es.js | 10 +-- src/libs/DateUtils.js | 44 +++++++++- .../AppNavigator/ModalStackNavigators.js | 7 ++ .../Profile/CustomStatus/SetTimePage.js | 85 +++++++++++++++++++ .../CustomStatus/StatusClearAfterPage.js | 48 ++++++----- 6 files changed, 172 insertions(+), 30 deletions(-) create mode 100644 src/pages/settings/Profile/CustomStatus/SetTimePage.js diff --git a/src/languages/en.js b/src/languages/en.js index 094487369f8a..586fafd9276f 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -896,10 +896,10 @@ export default { message: 'Message', timePeriods: { never: 'Never', - thirtyMinutes: 'Thirty minutes', - oneHour: 'One hour', - afterToday: 'After today', - afterWeek: 'After week', + thirtyMinutes: '30 minutes', + oneHour: '1 hour', + afterToday: 'Today', + afterWeek: 'A week', custom: 'Custom', }, untilTomorrow: 'Until tomorrow', diff --git a/src/languages/es.js b/src/languages/es.js index 37ef60152c6c..1315d02de363 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -900,13 +900,13 @@ export default { message: 'Mensaje', timePeriods: { never: 'Nunca', - thirtyMinutes: 'Treinta minutos', - oneHour: 'Una hora', - afterToday: 'Después de hoy', - afterWeek: 'Después de una semana', + thirtyMinutes: '30 minutos', + oneHour: '1 hora', + afterToday: 'Hoy', + afterWeek: 'Una semana', custom: 'Personalizado', - untilTomorrow: 'Hasta mañana', }, + untilTomorrow: 'Hasta mañana', untilTime: ({time}) => { // Check for HH:MM AM/PM format and starts with '01:' if (CONST.REGEX.TIME_STARTS_01.test(time)) { diff --git a/src/libs/DateUtils.js b/src/libs/DateUtils.js index 27c5e9397fb5..ac0537fc05d4 100644 --- a/src/libs/DateUtils.js +++ b/src/libs/DateUtils.js @@ -253,7 +253,15 @@ function extractDate(time) { * it's kind of 'newer' date */ function extractTime(dateTimeString) { - return moment(dateTimeString).format('HH:mm'); + return moment(dateTimeString).format('hh:mm'); +} +/** + * @param {string} dateTimeString + * @returns {string} example: 2023-05-16 + * it's kind of 'newer' date + */ +function extractTime12Hour(dateTimeString) { + return moment(dateTimeString).format('hh:mm A'); } /** @@ -284,7 +292,7 @@ function getDateBasedFromType(type) { default: return ''; } - } +} /** * receive date like 2020-05-16 05:34:14 and format it to show in string like "Until 05:34 PM" @@ -319,6 +327,36 @@ function getStatusUntilDate(inputDate) { return translateLocal('statusPage.untilTime', {time: input.format('YYYY-MM-DD hh:mm A')}); } +/** + * Update the hour and minute of a date. + * + * @param {string} time - Time in "hh:mm A" format (like "10:55 AM"). + * @param {string} date - Date in "YYYY-MM-DD HH:mm:ss" format. + * + * @returns {string} - Date with updated time in "YYYY-MM-DD HH:mm:ss" format. + */ +function setTimeOrDefaultToTomorrow(time, date) { + const dateToUse = date ? moment(date) : moment().endOf('day'); + + // Split the time into hour, minute, and period (AM/PM) + const [hour, minute, period] = time.match(/(\d+):(\d+) (\w\w)/).slice(1); + + // Adjust the hour based on the period + let adjustedHour = parseInt(hour, 10); + if (period === 'PM' && adjustedHour !== 12) { + adjustedHour += 12; + } else if (period === 'AM' && adjustedHour === 12) { + adjustedHour = 0; // For 12 AM, set to 00 + } + + // Set the hour, minute, and second + dateToUse.hour(adjustedHour); + dateToUse.minute(minute); + dateToUse.second(0); // Reset seconds to zero + + return dateToUse.format('YYYY-MM-DD HH:mm:ss'); +} + /** * @namespace DateUtils */ @@ -344,6 +382,8 @@ const DateUtils = { extractTime, formatDateTo12Hour, getStatusUntilDate, + extractTime12Hour, + setTimeOrDefaultToTomorrow, }; export default DateUtils; diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js index 63c83d9951b5..9480dc4aeae2 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js @@ -550,6 +550,13 @@ const SettingsModalStackNavigator = createModalStackNavigator([ }, name: 'Settings_Status_Clear_After_Custom', }, + { + getComponent: () => { + const Settings_Status_Clear_After_Time = require('../../../pages/settings/Profile/CustomStatus/SetTimePage').default; + return Settings_Status_Clear_After_Time; + }, + name: 'Settings_Status_Clear_After_Time', + }, { getComponent: () => { const WorkspaceInitialPage = require('../../../pages/workspace/WorkspaceInitialPage').default; diff --git a/src/pages/settings/Profile/CustomStatus/SetTimePage.js b/src/pages/settings/Profile/CustomStatus/SetTimePage.js new file mode 100644 index 000000000000..18f707ea22f2 --- /dev/null +++ b/src/pages/settings/Profile/CustomStatus/SetTimePage.js @@ -0,0 +1,85 @@ +import PropTypes from 'prop-types'; +import React, {useCallback} from 'react'; +import {withOnyx} from 'react-native-onyx'; +import lodashGet from 'lodash/get'; +import withCurrentUserPersonalDetails from '../../../../components/withCurrentUserPersonalDetails'; +import FullscreenLoadingIndicator from '../../../../components/FullscreenLoadingIndicator'; +import withLocalize, {withLocalizePropTypes} from '../../../../components/withLocalize'; +import HeaderWithBackButton from '../../../../components/HeaderWithBackButton'; +import ScreenWrapper from '../../../../components/ScreenWrapper'; +import TimePicker from '../../../../components/TimePicker'; +import usePrivatePersonalDetails from '../../../../hooks/usePrivatePersonalDetails'; +import Navigation from '../../../../libs/Navigation/Navigation'; +import * as User from '../../../../libs/actions/User'; +import DateUtils from '../../../../libs/DateUtils'; +import compose from '../../../../libs/compose'; +import ONYXKEYS from '../../../../ONYXKEYS'; +import ROUTES from '../../../../ROUTES'; + +const propTypes = { + /** User's private personal details */ + privatePersonalDetails: PropTypes.shape({ + dob: PropTypes.string, + }), + + ...withLocalizePropTypes, +}; + +const defaultProps = { + privatePersonalDetails: { + dob: '', + }, +}; + +function SetTimePage({translate, privatePersonalDetails, customStatus, currentUserPersonalDetails}) { + usePrivatePersonalDetails(); + + const clearAfter = lodashGet(currentUserPersonalDetails, 'status.clearAfter', ''); + const customDateTemporary = lodashGet(customStatus, 'customDateTemporary', ''); + const draftClearAfter = lodashGet(customStatus, 'clearAfter', ''); + + const onSubmitButtonPress = useCallback((time, amPmValue) => { + const timeToUse = DateUtils.setTimeOrDefaultToTomorrow(`${time} ${amPmValue}`, customDateTemporary); + + User.updateDraftCustomStatus({customDateTemporary: timeToUse}); + Navigation.goBack(ROUTES.SETTINGS_STATUS_CLEAR_AFTER); + }, [customDateTemporary]); + + + if (lodashGet(privatePersonalDetails, 'isLoading', true)) { + return ; + } + + const customStatusTime = DateUtils.extractTime(customDateTemporary || draftClearAfter || clearAfter); + + return ( + + Navigation.goBack(ROUTES.SETTINGS_STATUS_CLEAR_AFTER)} + /> + + + ); +} + +SetTimePage.propTypes = propTypes; +SetTimePage.defaultProps = defaultProps; +SetTimePage.displayName = 'SetTimePage'; + +export default compose( + withCurrentUserPersonalDetails, + withLocalize, + withOnyx({ + privatePersonalDetails: { + key: ONYXKEYS.PRIVATE_PERSONAL_DETAILS, + }, + customStatus: { + key: ONYXKEYS.CUSTOM_STATUS_DRAFT, + }, + }), +)(SetTimePage); diff --git a/src/pages/settings/Profile/CustomStatus/StatusClearAfterPage.js b/src/pages/settings/Profile/CustomStatus/StatusClearAfterPage.js index 4a30e98c87d1..9b208f9c3d14 100644 --- a/src/pages/settings/Profile/CustomStatus/StatusClearAfterPage.js +++ b/src/pages/settings/Profile/CustomStatus/StatusClearAfterPage.js @@ -16,16 +16,21 @@ import useLocalize from '../../../../hooks/useLocalize'; import ONYXKEYS from '../../../../ONYXKEYS'; import CONST from '../../../../CONST'; import * as User from '../../../../libs/actions/User'; -import withLocalize, {withLocalizePropTypes} from '../../../../components/withLocalize'; +import withLocalize from '../../../../components/withLocalize'; import compose from '../../../../libs/compose'; import DateUtils from '../../../../libs/DateUtils'; -import withCurrentUserPersonalDetails from '../../../../components/withCurrentUserPersonalDetails'; +import withCurrentUserPersonalDetails, { withCurrentUserPersonalDetailsDefaultProps } from '../../../../components/withCurrentUserPersonalDetails'; +import personalDetailsPropType from '../../../personalDetailsPropType'; -const defaultProps = {}; +const defaultProps = { + ...withCurrentUserPersonalDetailsDefaultProps, +}; -const propTypes = {}; +const propTypes = { + currentUserPersonalDetails: personalDetailsPropType, +}; -function StatusClearAfterPage({currentUserPersonalDetails, customStatus, ...props}) { +function StatusClearAfterPage({currentUserPersonalDetails, customStatus}) { const localize = useLocalize(); const clearAfter = lodashGet(currentUserPersonalDetails, 'status.clearAfter', ''); @@ -53,28 +58,33 @@ function StatusClearAfterPage({currentUserPersonalDetails, customStatus, ...prop calculatedDraftDate = DateUtils.getDateBasedFromType(selectedRange.value); } - User.updateDraftCustomStatus({clearAfter: calculatedDraftDate}); + User.updateDraftCustomStatus({clearAfter: calculatedDraftDate, customDateTemporary: calculatedDraftDate}); Navigation.goBack(ROUTES.SETTINGS_STATUS); }; - const updateMode = useCallback((mode) => { - if (mode.value === CONST.CUSTOM_STATUS_TYPES.CUSTOM) { - User.updateDraftCustomStatus({customDateTemporary: DateUtils.getOneHourFromNow()}); - } - setDraftPeriod(mode.value); - }, []); + const updateMode = useCallback( + (mode) => { + if (mode.value === draftPeriod) return; + User.updateDraftCustomStatus({ + customDateTemporary: mode.value === CONST.CUSTOM_STATUS_TYPES.CUSTOM ? DateUtils.getOneHourFromNow() : DateUtils.getDateBasedFromType(mode.value), + }); + setDraftPeriod(mode.value); + }, + [draftPeriod], + ); useEffect(() => { - if ((clearAfter && !draftClearAfter) || draftClearAfter) { - User.updateDraftCustomStatus({customDateTemporary: (clearAfter && !draftClearAfter) || draftClearAfter}); - return; - } - // value by default is CONST.CUSTOM_STATUS_TYPES.AFTER_TODAY - User.updateDraftCustomStatus({customDate: DateUtils.getEndOfToday()}); + User.updateDraftCustomStatus({ + customDateTemporary: clearAfter, + clearAfter, + }); + + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const customStatusDate = DateUtils.extractDate(customDateTemporary || draftClearAfter || clearAfter); - const customStatusTime = DateUtils.extractTime(customDateTemporary || draftClearAfter || clearAfter); + const customStatusTime = DateUtils.extractTime12Hour(customDateTemporary || clearAfter); + return ( Date: Tue, 22 Aug 2023 10:41:01 +0200 Subject: [PATCH 015/615] use correct clearAfter for saving --- src/pages/settings/Profile/CustomStatus/StatusPage.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pages/settings/Profile/CustomStatus/StatusPage.js b/src/pages/settings/Profile/CustomStatus/StatusPage.js index 28a2332db221..d9ec14f3fc7c 100644 --- a/src/pages/settings/Profile/CustomStatus/StatusPage.js +++ b/src/pages/settings/Profile/CustomStatus/StatusPage.js @@ -30,6 +30,7 @@ function StatusPage({draftStatus, currentUserPersonalDetails}) { const localize = useLocalize(); const currentUserEmojiCode = lodashGet(currentUserPersonalDetails, 'status.emojiCode', ''); const currentUserStatusText = lodashGet(currentUserPersonalDetails, 'status.text', ''); + const currentUserClearAfter = lodashGet(currentUserPersonalDetails, 'status.clearAfter', ''); const draftEmojiCode = lodashGet(draftStatus, 'emojiCode'); const draftText = lodashGet(draftStatus, 'text'); const draftClearAfter = lodashGet(draftStatus, 'clearAfter'); @@ -44,11 +45,11 @@ function StatusPage({draftStatus, currentUserPersonalDetails}) { const updateStatus = useCallback(() => { const endOfDay = moment().endOf('day').format('YYYY-MM-DD HH:mm:ss'); - User.updateCustomStatus({text: defaultText, emojiCode: defaultEmoji, clearAfter: endOfDay}); + User.updateCustomStatus({text: defaultText, emojiCode: defaultEmoji, clearAfter: draftClearAfter || endOfDay || currentUserClearAfter}); User.clearDraftCustomStatus(); Navigation.goBack(ROUTES.SETTINGS_PROFILE); - }, [defaultText, defaultEmoji]); + }, [defaultText, defaultEmoji, currentUserClearAfter, draftClearAfter]); const clearStatus = () => { User.clearCustomStatus(); From 55ecba368119bdc4d69ce9c2779aa2b971c3c9bc Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Wed, 23 Aug 2023 15:47:41 +0200 Subject: [PATCH 016/615] add styles to AmountTextInput --- src/components/AmountTextInput.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/components/AmountTextInput.js b/src/components/AmountTextInput.js index 8472ef271be0..de2296bd3833 100644 --- a/src/components/AmountTextInput.js +++ b/src/components/AmountTextInput.js @@ -25,12 +25,24 @@ const propTypes = { /** Function to call when selection in text input is changed */ onSelectionChange: PropTypes.func, + + /** Function to call when key is pressed in text input */ + onKeyPress: PropTypes.func, + + /** Style for the input */ + style: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]), + + /** Style for the container */ + containerStyles: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]), }; const defaultProps = { forwardedRef: undefined, selection: undefined, onSelectionChange: () => {}, + onKeyPress: () => {}, + style: {}, + containerStyles: {}, }; function AmountTextInput(props) { @@ -39,7 +51,7 @@ function AmountTextInput(props) { disableKeyboard autoGrow hideFocusedState - inputStyle={[styles.iouAmountTextInput, styles.p0, styles.noLeftBorderRadius, styles.noRightBorderRadius]} + inputStyle={[styles.iouAmountTextInput, styles.p0, styles.noLeftBorderRadius, styles.noRightBorderRadius, props.style]} textInputContainerStyles={[styles.borderNone, styles.noLeftBorderRadius, styles.noRightBorderRadius]} onChangeText={props.onChangeAmount} ref={props.forwardedRef} @@ -50,6 +62,8 @@ function AmountTextInput(props) { selection={props.selection} onSelectionChange={props.onSelectionChange} accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} + onKeyPress={props.onKeyPress} + containerStyles={props.containerStyles} /> ); } From 7cc9a4b172afc415becb645a4a9b0528e9154aef Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Wed, 23 Aug 2023 15:48:22 +0200 Subject: [PATCH 017/615] add isDisabledLongPress to button --- src/components/Button/index.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/components/Button/index.js b/src/components/Button/index.js index a850a43d2fb0..c0ea547a0aaa 100644 --- a/src/components/Button/index.js +++ b/src/components/Button/index.js @@ -120,6 +120,9 @@ const propTypes = { /** A ref to forward the button */ // eslint-disable-next-line react/forbid-prop-types forwardedRef: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({current: PropTypes.object})]), + + /** Whether the long press is disabled */ + isDisabledLongPress: PropTypes.bool, }; const defaultProps = { @@ -155,6 +158,7 @@ const defaultProps = { nativeID: '', accessibilityLabel: '', forwardedRef: undefined, + isDisabledLongPress: false, }; class Button extends Component { @@ -269,6 +273,7 @@ class Button extends Component { return this.props.onPress(e); }} onLongPress={(e) => { + if (this.props.isDisabledLongPress) return; if (this.props.shouldEnableHapticFeedback) { HapticFeedback.longPress(); } From c150adfd0146b8a151660914039df037aaedf243 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Wed, 23 Aug 2023 15:49:10 +0200 Subject: [PATCH 018/615] update BigNumberPad --- src/components/BigNumberPad.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/components/BigNumberPad.js b/src/components/BigNumberPad.js index 06dbc1aa34ac..60b8f1fc176e 100644 --- a/src/components/BigNumberPad.js +++ b/src/components/BigNumberPad.js @@ -17,12 +17,16 @@ const propTypes = { /** Used to locate this view from native classes. */ nativeID: PropTypes.string, + /** Whether long press is disabled */ + isDisabledLongPress: PropTypes.bool, + ...withLocalizePropTypes, }; const defaultProps = { longPressHandlerStateChanged: () => {}, nativeID: 'numPadView', + isDisabledLongPress: false, }; const padNumbers = [ @@ -83,6 +87,7 @@ function BigNumberPad(props) { props.longPressHandlerStateChanged(false); }} onMouseDown={(e) => e.preventDefault()} + isDisabledLongPress={props.isDisabledLongPress} /> ); })} From b6c34cb4fee0adf0e5715c83d695ffcd8ca3f97f Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Wed, 23 Aug 2023 15:49:50 +0200 Subject: [PATCH 019/615] create TimePicker --- src/components/TimePicker.js | 417 +++++++++++++++++++++++++++++++++++ src/libs/DateUtils.js | 19 ++ 2 files changed, 436 insertions(+) create mode 100644 src/components/TimePicker.js diff --git a/src/components/TimePicker.js b/src/components/TimePicker.js new file mode 100644 index 000000000000..8195d88fcddc --- /dev/null +++ b/src/components/TimePicker.js @@ -0,0 +1,417 @@ +import React, {useEffect, useState, useCallback, useRef, useMemo} from 'react'; +import {View} from 'react-native'; +import PropTypes from 'prop-types'; +import lodashGet from 'lodash/get'; +import _ from 'underscore'; +import styles from '../styles/styles'; +import themeColors from '../styles/themes/default'; +import BigNumberPad from './BigNumberPad'; +import Button from './Button'; +import AmountTextInput from './AmountTextInput'; +import * as DeviceCapabilities from '../libs/DeviceCapabilities'; +import useLocalize from '../hooks/useLocalize'; +// import CONST from '../../../CONST'; +import CONST from '../CONST'; +import DateUtils from '../libs/DateUtils'; +import Text from './Text'; +import useKeyboardShortcut from '../hooks/useKeyboardShortcut'; + +const propTypes = { + /** Refs forwarded to the TextInputWithCurrencySymbol */ + forwardedRef: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({current: PropTypes.instanceOf(React.Component)})]), + + /** Fired when submit button pressed, saves the given amount and navigates to the next page */ + onSubmitButtonPress: PropTypes.func, + + /** Default value for the inputs */ + defaultValue: PropTypes.string, +}; + +const defaultProps = { + forwardedRef: null, + onSubmitButtonPress: () => {}, + defaultValue: '', +}; + +const AMOUNT_VIEW_ID = 'amountView'; +const NUM_PAD_CONTAINER_VIEW_ID = 'numPadContainerView'; +const NUM_PAD_VIEW_ID = 'numPadView'; + +function formatHour(hourText) { + // // If the integer value of hour is greater than 12, return the second digit with a leading 0 + + // return twoDigitHour; + const hourNumber = parseInt(hourText, 10); + + // If the integer value of hour is greater than 12, subtract 10 + const adjustedHour = hourNumber > 12 ? hourNumber - 10 : hourNumber; + + // Convert to a string and pad with a 0 if needed + return adjustedHour.toString().padStart(2, '0'); +} + +function insertAtPosition(originalString, newSubstring, selectionPositionFrom, selectionPositionTo, fn) { + // Check for invalid positions + if (selectionPositionFrom < 0 || selectionPositionTo < 0 || selectionPositionFrom > originalString.length || selectionPositionTo > originalString.length) { + return; + } + + // If the positions are the same, it means we're inserting at a point + if (selectionPositionFrom === selectionPositionTo) { + if (selectionPositionFrom === originalString.length) { + if (typeof fn === 'function') fn(); + return originalString; // If the insertion point is at the end, simply return the original string + } + return originalString.slice(0, selectionPositionFrom) + newSubstring + originalString.slice(selectionPositionFrom); + } + + // Replace the selected range + return originalString.slice(0, selectionPositionFrom) + newSubstring + originalString.slice(selectionPositionTo); +} + +function decreaseBothSelectionByOne({start, end}) { + if (start === 0) { + return {start: 0, end: 0}; + } + return {start: start - 1, end: end - 1}; +} + +function replaceWithZeroAtPosition(originalString, position) { + if (position === 0 || position > 2) { + return originalString; + } + return `${originalString.slice(0, position - 1)}0${originalString.slice(position)}`; +} +function TimePicker({forwardedRef, onSubmitButtonPress, defaultValue}) { + const {translate, numberFormat} = useLocalize(); + + const [hours, setHours] = useState(DateUtils.parseTimeTo12HourFormat(defaultValue).hour); + const [minute, setMinute] = useState(DateUtils.parseTimeTo12HourFormat(defaultValue).minute); + const [selectionHour, setSelectionHour] = useState({start: 0, end: 0}); + const [selectionMinute, setSelectionMinute] = useState({start: 0, end: 0}); + + const [amPmValue, setAmPmValue] = useState('AM'); + + const hourInputRef = useRef(null); + const minuteInputRef = useRef(null); + + /** + * Submit amount and navigate to a proper page + * + */ + const submitAndNavigateToNextPage = useCallback(() => { + onSubmitButtonPress(`${hours}:${minute}`, amPmValue); + }, [hours, minute, onSubmitButtonPress, amPmValue]); + + const focusMinuteInputOnFirstCharacter = useCallback(() => { + minuteInputRef.current.focus(); + setSelectionMinute({start: 0, end: 0}); + }, []); + const focusHourInputIfNeeded = useCallback(({nativeEvent}, forceFocus = false) => { + const keyValue = lodashGet(nativeEvent, 'key', ''); + if (!forceFocus && keyValue !== 'Backspace') return; + + hourInputRef.current.focus(); + }, []); + + const handleHourChange = (text) => { + let filteredText; + if (selectionHour.start !== selectionHour.end) { + filteredText = text.replace(/[^0-9]/g, '').slice(0, 2); + } else { + filteredText = text.replace(/[^0-9]/g, ''); + } + let newHour = hours; + let newSelection = selectionHour.start; + if (selectionHour.start !== selectionHour.end) { + if (filteredText.length === 1 && filteredText <= 1) { + newHour = `${filteredText}0`; + newSelection = 1; + } else { + newHour = `${formatHour(filteredText)}`; + newSelection = 2; + focusMinuteInputOnFirstCharacter(); + } + } else if (selectionHour.start === 0) { + const formattedText = `${filteredText[0]}${filteredText[2] || 0}`; + if (formattedText > 12) { + newHour = `0${formattedText[1]}`; + newSelection = 2; + focusMinuteInputOnFirstCharacter(); + } else { + newHour = `${formattedText[0]}${formattedText[1]}`; + newSelection = 1; + } + } else if (selectionHour.start === 1) { + // if we remove value + if (filteredText.length < 2) { + newHour = `0${text}`; + newSelection = 0; + } else if (text[1] > 2) { + newHour = `0${text[1]}`; + newSelection = 2; + focusMinuteInputOnFirstCharacter(); + } else { + newHour = `${text[0]}${text[1]}`; + newSelection = 2; + focusMinuteInputOnFirstCharacter(); + } + } else if (selectionHour.start === 2 && selectionHour.end === 2) { + if (filteredText.length < 2) { + newHour = `${text}0`; + newSelection = 1; + } else { + focusMinuteInputOnFirstCharacter(); + } + } + + setHours(newHour); + setSelectionHour({start: newSelection, end: newSelection}); + }; + + const handleMinutesChange = (text) => { + const filteredText = text.replace(/[^0-9]/g, ''); + + let newMinute = minute; + let newSelection = selectionMinute.start; + if (selectionMinute.start !== selectionMinute.end) { + if (filteredText.length === 1 && filteredText > 5) { + newMinute = `0${filteredText}`; + newSelection = 2; + } else if (filteredText.length === 1 && filteredText <= 5) { + newMinute = `${filteredText}0`; + newSelection = 1; + } else { + newMinute = `${filteredText.slice(0, 2)}`; + newSelection = 2; + } + } else if (selectionMinute.start === 0) { + const formattedText = `${filteredText[0]}${filteredText[2] || 0}`; + if (text[0] >= 6) { + newMinute = `0${formattedText[1]}`; + newSelection = 2; + } else { + newMinute = `${formattedText[0]}${formattedText[1]}`; + newSelection = 1; + } + } else if (selectionMinute.start === 1) { + // //if we remove value + if (filteredText.length < 2) { + newMinute = `0${text}`; + newSelection = 0; + } else { + newMinute = `${text[0]}${text[1]}`; + newSelection = 2; + } + } else if (filteredText.length < 2) { + newMinute = `${text}0`; + newSelection = 1; + } + + setMinute(newMinute); + setSelectionMinute({start: newSelection, end: newSelection}); + }; + + /** + * Update amount with number or Backspace pressed for BigNumberPad. + * Validate new amount with decimal number regex up to 6 digits and 2 decimal digit to enable Next button + * + * @param {String} key + */ + const updateAmountNumberPad = useCallback( + (key) => { + const isHourFocused = hourInputRef.current.isFocused(); + const isMinuteFocused = minuteInputRef.current.isFocused(); + if (!isHourFocused && !isMinuteFocused) { + hourInputRef.current.focus(); + setTimeout(() => setSelectionHour({start: 0, end: 0}), 10); + } + + if (key === '.') return; + if (key === '<' || key === 'Backspace') { + if (isHourFocused) { + const newHour = replaceWithZeroAtPosition(hours, selectionHour.start); + setHours(newHour); + setSelectionHour(decreaseBothSelectionByOne(selectionHour)); + } else if (isMinuteFocused) { + if (selectionMinute.start === 0) { + focusHourInputIfNeeded(_, true); + } + const newMinute = replaceWithZeroAtPosition(minute, selectionMinute.start); + setMinute(newMinute); + setSelectionMinute(decreaseBothSelectionByOne(selectionMinute)); + } + return; + } + const trimmedKey = key.replace(/[^0-9]/g, ''); + if (isHourFocused) { + handleHourChange(insertAtPosition(hours, trimmedKey, selectionHour.start, selectionHour.end)); + } else if (isMinuteFocused) { + handleMinutesChange(insertAtPosition(minute, trimmedKey, selectionMinute.start, selectionMinute.end)); + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [minute, hours, selectionHour, selectionMinute], + ); + + useEffect(() => minuteInputRef.current.focus, []); + + const arrowConfig = useMemo( + () => ({ + shouldPreventDefault: false, + }), + [], + ); + + const arrowLeftCallback = useCallback(() => { + const isMinuteFocused = minuteInputRef.current.isFocused(); + if (isMinuteFocused && selectionMinute.start === 0) { + focusHourInputIfNeeded(_, true); + } + }, [selectionHour, selectionMinute]); + const arrowRightCallback = useCallback(() => { + const isHourFocused = hourInputRef.current.isFocused(); + + if (isHourFocused && selectionHour.start === 2) { + focusMinuteInputOnFirstCharacter(); + } + }, [selectionHour, selectionMinute]); + + useKeyboardShortcut(CONST.KEYBOARD_SHORTCUTS.ARROW_LEFT, arrowLeftCallback, arrowConfig); + useKeyboardShortcut(CONST.KEYBOARD_SHORTCUTS.ARROW_RIGHT, arrowRightCallback, arrowConfig); + // useKeyboardShortcut(CONST.KEYBOARD_SHORTCUTS., arrowRightCallback, arrowConfig); + + const handleFocusOnBackspace = useCallback( + (e) => { + if (selectionMinute.start !== 0 || e.key === 'Backspace') return; + focusHourInputIfNeeded(e); + }, + [selectionMinute.start], + ); + + return ( + <> + + { + if (typeof forwardedRef === 'function') { + forwardedRef({refHour: ref, minuteRef: minuteInputRef.current}); + } else if (forwardedRef && _.has(forwardedRef, 'current')) { + // eslint-disable-next-line no-param-reassign + forwardedRef.current = {hourRef: ref, minuteRef: minuteInputRef.current}; + } + hourInputRef.current = ref; + }} + maxLength={2} + onSelectionChange={(e) => { + setSelectionHour(e.nativeEvent.selection); + }} + selection={selectionHour} + style={{ + fontSize: 69, + minWidth: 56, + }} + containerStyles={[{height: 100}]} + /> + : + { + if (typeof forwardedRef === 'function') { + forwardedRef({refHour: hourInputRef.current, minuteRef: ref}); + } else if (forwardedRef && _.has(forwardedRef, 'current')) { + // eslint-disable-next-line no-param-reassign + minuteInputRef.current = {hourRef: hourInputRef.current, minuteInputRef: ref}; + } + minuteInputRef.current = ref; + }} + maxLength={2} + onSelectionChange={(e) => { + setSelectionMinute(e.nativeEvent.selection); + }} + selection={selectionMinute} + style={{ + fontSize: 69, + minWidth: 56, + }} + containerStyles={[{height: 100}]} + /> + + +