From f31e802c8c91ad3c30bcb7b04c6e3b3aec7b4acb Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Fri, 16 Jul 2021 02:50:26 +0530 Subject: [PATCH 01/84] add test for Translations keys match --- tests/unit/TranslateTest.js | 39 ++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/tests/unit/TranslateTest.js b/tests/unit/TranslateTest.js index f46a93bc3ad56..5f9338f1d893a 100644 --- a/tests/unit/TranslateTest.js +++ b/tests/unit/TranslateTest.js @@ -1,7 +1,9 @@ +const _ = require('underscore'); const translate = require('../../src/libs/translate'); -const translations = require('../../src/languages/translations'); const CONFIG = require('../../src/CONFIG'); +const translations = require('../../src/languages/translations'); +const originalTranslations = _.clone(translations); translations.default = { en: { testKey1: 'English', @@ -52,3 +54,38 @@ describe('translate', () => { expect(translate.translate('en', ['testKeyGroup', 'testFunction'], {testVariable})).toBe(expectedValue); }); }); + +describe('Translation Keys', () => { + let activeLanguage; + let path = ''; + function matchKeys(source, target, key) { + path += key ? `${key}.` : ''; + const pathLevel = path; + if (key && !_.has(target, key)) { + console.debug(`🏹 ${path.slice(0, -1)} is missing from ${activeLanguage}.js`); + return; + } + const sourceOBJ = key ? source[key] : source; + const targetOBJ = key ? target[key] : target; + if (_.isObject(sourceOBJ) && !_.isFunction(sourceOBJ)) { + return _.every(_.keys(sourceOBJ), (subKey) => { + path = pathLevel; + return matchKeys(sourceOBJ, targetOBJ, subKey); + }); + } + if (key) { + path = path.slice(0, -(key.length - 1)); + } + return true; + } + it('Does each locale has all the keys', () => { + const excludeLanguages = ['en', 'es-ES']; + const languages = _.without(_.keys(originalTranslations.default), ...excludeLanguages); + const parentLanguage = originalTranslations.default.en; + const hasAllKeys = _.every(languages, (ln) => { + activeLanguage = ln; + return matchKeys(parentLanguage, originalTranslations.default[ln]); + }); + expect(hasAllKeys).toBeTruthy(); + }); +}); From 9e0fe2ae47fe263896db1e37e65eecc0063214f5 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Sat, 17 Jul 2021 23:05:28 +0530 Subject: [PATCH 02/84] added translations --- src/languages/es.js | 123 +++++++++++++++++++++++++++++++------------- 1 file changed, 88 insertions(+), 35 deletions(-) diff --git a/src/languages/es.js b/src/languages/es.js index 678cdd8b25367..d5d3f7f35dcd3 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -21,10 +21,14 @@ export default { not: 'No', signIn: 'Conectarse', continue: 'Continuar', + firstName: 'Primer nombre', + lastName: 'Apellido', phoneNumber: 'Número de teléfono', email: 'Email', and: 'y', details: 'Detalles', + privacy: 'Intimidad', + privacyPolicy: 'Política de privacidad', delete: 'Eliminar', contacts: 'Contactos', recents: 'Recientes', @@ -103,10 +107,13 @@ export default { writeSomething: 'Escribe algo...', blockedFromConcierge: 'Comunicación no permitida', youAppearToBeOffline: 'Parece que estás desconectado.', + fileUploadFailed: 'Subida fallida. El archivo no es compatible.', }, - reportActionContextMenu: { + contextMenuItem: { copyToClipboard: 'Copiar al Portapapeles', copied: '¡Copiado!', + }, + reportActionContextMenu: { copyLink: 'Copiar Enlace', markAsUnread: 'Marcar como no leído', editComment: 'Editar Commentario', @@ -135,6 +142,7 @@ export default { confirm: 'Confirmar', splitBill: 'Dividir Factura', requestMoney: 'Pedir Dinero', + sendMoney: 'Enviar Dinero', pay: 'Pagar', viewDetails: 'Ver detalles', settleExpensify: 'Pagar con Expensify', @@ -146,6 +154,7 @@ export default { owes: ({manager, owner}) => `${manager} debe a ${owner}`, paid: ({owner, manager}) => `${manager} pagó a ${owner}`, split: ({amount}) => `Dividir ${amount}`, + send: ({amount}) => `Enviar ${amount}`, choosePaymentMethod: 'Elige el método de pago:', noReimbursableExpenses: 'El monto de este informe es inválido', }, @@ -230,7 +239,8 @@ export default { enterYourUsernameToGetPaidViaPayPal: 'Escribe tu nombre de usuario para que otros puedan pagarte a través de PayPal.', payPalMe: 'PayPal.me/', yourPayPalUsername: 'Tu usuario de PayPal', - addPayPalAccount: 'Agregar Cuenta de Paypal', + addPayPalAccount: 'Agregar Cuenta de PayPal', + growlMessageOnSave: 'Su nombre de usuario de PayPal se agregó correctamente', }, paymentMethodList: { addPaymentMethod: 'Agrega método de pago', @@ -255,6 +265,14 @@ export default { expensifyIsOpenSource: 'Expensify.cash es open source', theCode: 'el código', openJobs: 'trabajos disponibles', + heroHeading: 'Dividir cuentas\ny chatear con amigos.', + heroDescription: { + phrase1: 'El dinero habla. Y ahora que el chat y los pagos están en un solo lugar, también es fácil. Sus pagos le llegan tan rápido como puede transmitir su punto.', + phrase2: 'New Expensify es de código abierto. Vista', + phrase3: 'el código', + phrase4: 'Vista', + phrase5: 'vacantes', + }, }, termsOfUse: { phrase1: 'Al usar Expensify.cash, estás aceptando los', @@ -263,9 +281,11 @@ export default { phrase4: 'política de privacidad', phrase5: 'El envío de dinero es brindado por Expensify Payments LLC (NMLS ID:2017010) de conformidad con sus', phrase6: 'licencias', + phrase7: 'licenses', }, passwordForm: { pleaseFillOutAllFields: 'Por favor completa todos los campos', + enterYourTwoFactorAuthenticationCodeToContinue: 'Ingrese su código de autenticación de dos factores para continuar', forgot: '¿Te has olvidado?', twoFactorCode: 'Autenticación de 2 factores', requiredWhen2FAEnabled: 'Obligatorio cuando A2F está habilitado', @@ -301,7 +321,10 @@ export default { }, setPasswordPage: { enterPassword: 'Escribe una contraseña', + confirmNewPassword: 'Confirma la contraseña', setPassword: 'Configura tu Contraseña', + passwordsDontMatch: 'Las contraseñas deben coincidir', + newPasswordPrompt: 'Su contraseña debe tener al menos 8 caracteres, \n1 letra mayúscula, 1 letra minúscula, 1 número.', }, bankAccount: { accountNumber: 'Número de cuenta', @@ -356,6 +379,69 @@ export default { noPhoneNumber: 'Por favor escribe un número de teléfono que incluya el código de país e.g +447814266907', maxParticipantsReached: 'Has llegado al número máximo de participantes para un grupo.', }, + 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.', + }, + additionalDetailsStep: { + headerTitle: 'Detalles adicionales', + helpText: 'Necesitamos confirmar la siguiente información antes de que podamos procesar este pago.', + helpLink: 'Obtenga más información sobre por qué necesitamos esto.', + legalFirstNameLabel: 'Primer nombre legal', + legalMiddleNameLabel: 'Segundo nombre legal', + legalLastNameLabel: 'Apellido legal', + }, + 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', + termsMustBeAccepted: 'Se deben aceptar los términos', + }, + activateStep: { + headerTitle: 'Habilitar pagos', + activated: 'Su billetera Expensify está lista para usar.', + checkBackLater: 'Todavía estamos revisando tu información. Por favor, vuelva más tarde.', + }, + companyStep: { + headerTitle: 'Información de la Empresa', + subtitle: 'Dé más información sobre su empresa.', + legalBusinessName: 'Nombre Comercial Legal', + companyWebsite: 'Company Website', + taxIDNumber: 'Tax ID Number', + companyType: 'Página Web de la Empresa', + incorporationDate: 'Fecha 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)', + companyPhonePlaceholder: '10 dígitos, sin guiones', + }, + requestorStep: { + headerTitle: 'Información del solicitante', + financialRegulations: 'Las leyes fiscales y el reglamento bancario nos obliga a verificar la identidad de todo individuo que desee añadir una cuenta bancaria representando a una compañía. ', + 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 ', + onFidoFacialScan: 'Onfido’s Facial Scan Policy and Release', + facialScan: 'la política de reconocimiento facial y la exención de Onfido', + 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', + buttonText: 'Finalizar Configuración', + maxAttemptError: 'Se ha inhabilitado la validación de esta cuenta bancaria, debido a demasiados intentos incorrectos. Por favor contáctenos.', + description: 'Uno o dos días después de agregar su cuenta a Expensify, enviamos tres (3) transacciones a su cuenta. Tienen una línea comercial como "Expensify, Inc. Validation"', + descriptionCTA: 'Ingrese el monto de cada transacción en los campos a continuación. Ejemplo: 1.51', + reviewingInfo: '¡Gracias! Estamos revisando tu información y nos comunicaremos contigo en breve. Consulte su chat con Concierge ', + forNextSteps: ' para conocer los próximos pasos para terminar de configurar su cuenta bancaria.', + }, beneficialOwnersStep: { beneficialOwners: 'Beneficial Owners', additionalInformation: 'Additional Information', @@ -416,39 +502,6 @@ export default { welcomeNote: ({workspaceName}) => `¡Has sido invitado a la ${workspaceName} Espacio de trabajo! Descargue la aplicación móvil Expensify para comenzar a rastrear sus gastos.`, }, }, - companyStep: { - headerTitle: 'Información de la Empresa', - subtitle: 'Dé más información sobre su empresa.', - legalBusinessName: 'Nombre Comercial Legal', - companyWebsite: 'Company Website', - taxIDNumber: 'Tax ID Number', - companyType: 'Página Web de la Empresa', - incorporationDate: 'Fecha 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)', - companyPhonePlaceholder: '10 dígitos, sin guiones', - }, - validationStep: { - headerTitle: 'Validar', - buttonText: 'Finalizar Configuración', - maxAttemptError: 'Se ha inhabilitado la validación de esta cuenta bancaria, debido a demasiados intentos incorrectos. Por favor contáctenos.', - description: 'Uno o dos días después de agregar su cuenta a Expensify, enviamos tres (3) transacciones a su cuenta. Tienen una línea comercial como "Expensify, Inc. Validation"', - descriptionCTA: 'Ingrese el monto de cada transacción en los campos a continuación. Ejemplo: 1.51', - reviewingInfo: '¡Gracias! Estamos revisando tu información y nos comunicaremos contigo en breve. Consulte su chat con Concierge ', - forNextSteps: ' para conocer los próximos pasos para terminar de configurar su cuenta bancaria.', - }, - requestorStep: { - headerTitle: 'Información del solicitante', - financialRegulations: 'Las leyes fiscales y el reglamento bancario nos obliga a verificar la identidad de todo individuo que desee añadir una cuenta bancaria representando a una compañía. ', - 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 ', - facialScan: 'la política de reconocimiento facial y la exención de Onfido', - 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', - }, requestCallPage: { requestACall: 'Llámame por teléfono', description: '¿Necesitas ayuda configurando tu cuenta? Nuestro equipo de guías puede ayudarte.', From a53f43fdc71aaeb91c11a168b4763e4c30390901 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Thu, 22 Jul 2021 04:35:27 +0530 Subject: [PATCH 03/84] adjust outline for focused elments --- web/index.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/web/index.html b/web/index.html index df4c09c794445..1c55f9bffbc4e 100644 --- a/web/index.html +++ b/web/index.html @@ -32,6 +32,9 @@ -webkit-user-select: none !important; -webkit-touch-callout: none !important; } + *:focus, *:focus-visible, [data-focusvisible-polyfill] { + outline: 1px solid #0185ff; + } From 59b378f817cd3d632c5f7389a533530355143dc8 Mon Sep 17 00:00:00 2001 From: Ali Abbas Malik Date: Wed, 21 Jul 2021 20:48:36 -0400 Subject: [PATCH 04/84] [IS-3599] Fixed crash issue when editing a message --- src/pages/home/report/ReportActionItemMessageEdit.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index 9db04402951c0..ba9521ff9fbe5 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -68,9 +68,8 @@ class ReportActionItemMessageEdit extends React.Component { */ updateDraft(newDraft) { this.textInput.setNativeProps({text: newDraft}); - const trimmedNewDraft = newDraft.trim(); - this.setState({draft: trimmedNewDraft}); - this.debouncedSaveDraft(trimmedNewDraft); + this.setState({draft: newDraft}); + this.debouncedSaveDraft(newDraft); } /** From d8692156dd0006f44a181a3a2520b2c94e173808 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Fri, 23 Jul 2021 04:22:30 +0530 Subject: [PATCH 05/84] Context-menu repositioned --- .../report/ContextMenu/ContextMenuItems.js | 98 +++++++ .../MiniReportActionContextMenuPropsTypes.js | 37 +++ .../MiniReportActionContextMenu/index.js | 132 +++++++++ .../index.native.js | 1 + .../ReportActionContextMenu.js | 60 +--- .../ContextMenu/ReportContextMenuContext.js | 5 + src/pages/home/report/ReportActionItem.js | 260 ++++------------- src/pages/home/report/ReportActionsView.js | 273 ++++++++++++++++-- 8 files changed, 587 insertions(+), 279 deletions(-) create mode 100644 src/pages/home/report/ContextMenu/ContextMenuItems.js create mode 100644 src/pages/home/report/ContextMenu/MiniReportActionContextMenu/MiniReportActionContextMenuPropsTypes.js create mode 100644 src/pages/home/report/ContextMenu/MiniReportActionContextMenu/index.js create mode 100644 src/pages/home/report/ContextMenu/MiniReportActionContextMenu/index.native.js rename src/pages/home/report/{ => ContextMenu}/ReportActionContextMenu.js (76%) create mode 100644 src/pages/home/report/ContextMenu/ReportContextMenuContext.js diff --git a/src/pages/home/report/ContextMenu/ContextMenuItems.js b/src/pages/home/report/ContextMenu/ContextMenuItems.js new file mode 100644 index 0000000000000..db51ca13fe376 --- /dev/null +++ b/src/pages/home/report/ContextMenu/ContextMenuItems.js @@ -0,0 +1,98 @@ +import _ from 'underscore'; +import lodashGet from 'lodash/get'; +import Str from 'expensify-common/lib/str'; +import { + Clipboard as ClipboardIcon, LinkCopy, Mail, Pencil, Trashcan, Checkmark, +} from '../../../../components/Icon/Expensicons'; +import { + setNewMarkerPosition, updateLastReadActionID, saveReportActionDraft, +} from '../../../../libs/actions/Report'; +import Clipboard from '../../../../libs/Clipboard'; +import {isReportMessageAttachment, canEditReportAction, canDeleteReportAction} from '../../../../libs/reportUtils'; +import ReportActionComposeFocusManager from '../../../../libs/ReportActionComposeFocusManager'; + +// A list of all the context actions in this menu. +const contextActions = [ + // Copy to clipboard + { + textTranslationKey: 'contextMenuItem.copyToClipboard', + icon: ClipboardIcon, + successTextTranslationKey: 'contextMenuItem.copied', + successIcon: Checkmark, + shouldShow: true, + + // If return value is true, we switch the `text` and `icon` on + // `ContextMenuItem` with `successText` and `successIcon` which will fallback to + // the `text` and `icon` + onPress: (reportAction, selection, hidePopover) => { + const message = _.last(lodashGet(reportAction, 'message', null)); + const html = lodashGet(message, 'html', ''); + const text = Str.htmlDecode(selection || lodashGet(message, 'text', '')); + const isAttachment = _.has(reportAction, 'isAttachment') + ? reportAction.isAttachment + : isReportMessageAttachment(text); + if (!isAttachment) { + Clipboard.setString(text); + } else { + Clipboard.setString(html); + } + hidePopover(true, ReportActionComposeFocusManager.focus); + }, + }, + + { + textTranslationKey: 'reportActionContextMenu.copyLink', + icon: LinkCopy, + shouldShow: false, + onPress: () => {}, + }, + + { + textTranslationKey: 'reportActionContextMenu.markAsUnread', + icon: Mail, + successIcon: Checkmark, + shouldShow: true, + onPress: (reportID, reportAction) => { + updateLastReadActionID(reportID, reportAction.sequenceNumber); + setNewMarkerPosition(reportID, reportAction.sequenceNumber); + this.hidePopover(true, ReportActionComposeFocusManager.focus); + }, + }, + + { + textTranslationKey: 'reportActionContextMenu.editComment', + icon: Pencil, + shouldShow: reportAction => canEditReportAction(reportAction), + onPress: (reportID, reportAction, draftMessage, isMini, hidePopover) => { + const editAction = () => saveReportActionDraft( + reportID, + reportAction.reportActionID, + _.isEmpty(draftMessage) ? this.getActionText() : '', + ); + + if (isMini) { + // No popover to hide, call editAction immediately + editAction(); + } else { + // Hide popover, then call editAction + hidePopover(false, editAction); + } + }, + }, + { + textTranslationKey: 'reportActionContextMenu.deleteComment', + icon: Trashcan, + shouldShow: reportAction => canDeleteReportAction(reportAction), + onPress: (isMini, showDeleteConfirmModal, hidePopover) => { + if (isMini) { + // No popover to hide, call showDeleteConfirmModal immediately + showDeleteConfirmModal(); + } else { + // Hide popover, then call showDeleteConfirmModal + hidePopover(false, showDeleteConfirmModal); + } + }, + }, +]; + +export default contextActions; diff --git a/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/MiniReportActionContextMenuPropsTypes.js b/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/MiniReportActionContextMenuPropsTypes.js new file mode 100644 index 0000000000000..c603fe4a58f83 --- /dev/null +++ b/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/MiniReportActionContextMenuPropsTypes.js @@ -0,0 +1,37 @@ +import PropTypes from 'prop-types'; +import {withLocalizePropTypes} from '../../../../../components/withLocalize'; +import ReportActionPropTypes from '../../ReportActionPropTypes'; + +const propTypes = { + /** The ID of the report this report action is attached to. */ + // eslint-disable-next-line react/no-unused-prop-types + reportID: PropTypes.number.isRequired, + + /** The report action this context menu is attached to. */ + reportAction: PropTypes.shape(ReportActionPropTypes).isRequired, + + /** Controls the visibility of this component. */ + isVisible: PropTypes.bool, + + /** The copy selection of text. */ + selection: PropTypes.string, + + /** Draft message - if this is set the comment is in 'edit' mode */ + draftMessage: PropTypes.string, + + /** Function to show the delete Action confirmation modal */ + showDeleteConfirmModal: PropTypes.func.isRequired, + + ...withLocalizePropTypes, +}; + +const defaultProps = { + isVisible: false, + selection: '', + draftMessage: '', +}; + +export { + propTypes, + defaultProps, +}; diff --git a/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/index.js b/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/index.js new file mode 100644 index 0000000000000..17d68ca11a899 --- /dev/null +++ b/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/index.js @@ -0,0 +1,132 @@ +import _ from 'underscore'; +import React from 'react'; +import {View} from 'react-native'; +import lodashGet from 'lodash/get'; +import Str from 'expensify-common/lib/str'; +import { + Clipboard as ClipboardIcon, LinkCopy, Mail, Pencil, Trashcan, Checkmark, +} from '../../../../../components/Icon/Expensicons'; +import getReportActionContextMenuStyles from '../../../../../styles/getReportActionContextMenuStyles'; +import { + setNewMarkerPosition, updateLastReadActionID, saveReportActionDraft, +} from '../../../../../libs/actions/Report'; +import ContextMenuItem from '../../../../../components/ContextMenuItem'; +import {propTypes, defaultProps} from './MiniReportActionContextMenuPropsTypes'; +import Clipboard from '../../../../../libs/Clipboard'; +import compose from '../../../../../libs/compose'; +import {isReportMessageAttachment, canEditReportAction, canDeleteReportAction} from '../../../../../libs/reportUtils'; +import withLocalize from '../../../../../components/withLocalize'; +import ReportActionComposeFocusManager from '../../../../../libs/ReportActionComposeFocusManager'; + +class MiniReportActionContextMenu extends React.Component { + constructor(props) { + super(props); + + this.getActionText = this.getActionText.bind(this); + + // A list of all the context actions in this menu. + this.contextActions = [ + // Copy to clipboard + { + text: this.props.translate('contextMenuItem.copyToClipboard'), + icon: ClipboardIcon, + successText: this.props.translate('contextMenuItem.copied'), + successIcon: Checkmark, + shouldShow: true, + + // If return value is true, we switch the `text` and `icon` on + // `ContextMenuItem` with `successText` and `successIcon` which will fallback to + // the `text` and `icon` + onPress: () => { + const message = _.last(lodashGet(this.props.reportAction, 'message', null)); + const html = lodashGet(message, 'html', ''); + const text = Str.htmlDecode(props.selection || lodashGet(message, 'text', '')); + const isAttachment = _.has(this.props.reportAction, 'isAttachment') + ? this.props.reportAction.isAttachment + : isReportMessageAttachment(text); + if (!isAttachment) { + Clipboard.setString(text); + } else { + Clipboard.setString(html); + } + this.props.hidePopover(true, ReportActionComposeFocusManager.focus); + }, + }, + + { + text: this.props.translate('reportActionContextMenu.copyLink'), + icon: LinkCopy, + shouldShow: false, + onPress: () => {}, + }, + + { + text: this.props.translate('reportActionContextMenu.markAsUnread'), + icon: Mail, + successIcon: Checkmark, + shouldShow: true, + onPress: () => { + updateLastReadActionID(this.props.reportID, this.props.reportAction.sequenceNumber); + setNewMarkerPosition(this.props.reportID, this.props.reportAction.sequenceNumber); + this.props.hidePopover(true, ReportActionComposeFocusManager.focus); + }, + }, + + { + text: this.props.translate('reportActionContextMenu.editComment'), + icon: Pencil, + shouldShow: () => canEditReportAction(this.props.reportAction), + onPress: () => { + saveReportActionDraft( + this.props.reportID, + this.props.reportAction.reportActionID, + _.isEmpty(this.props.draftMessage) ? this.getActionText() : '', + ); + }, + }, + { + text: this.props.translate('reportActionContextMenu.deleteComment'), + icon: Trashcan, + shouldShow: () => canDeleteReportAction(this.props.reportAction), + onPress: () => { + this.props.showDeleteConfirmModal(); + }, + }, + ]; + } + + /** + * Gets the markdown version of the message in an action. + * + * @return {String} + */ + getActionText() { + const message = _.last(lodashGet(this.props.reportAction, 'message', null)); + return lodashGet(message, 'html', ''); + } + + render() { + return this.props.isVisible && ( + + {this.contextActions.map(contextAction => _.result(contextAction, 'shouldShow', false) && ( + contextAction.onPress(this.props.reportAction)} + /> + ))} + + ); + } +} + +MiniReportActionContextMenu.propTypes = propTypes; +MiniReportActionContextMenu.defaultProps = defaultProps; + +export default compose( + withLocalize, +)(MiniReportActionContextMenu); diff --git a/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/index.native.js b/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/index.native.js new file mode 100644 index 0000000000000..461f67a0a4bcb --- /dev/null +++ b/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/index.native.js @@ -0,0 +1 @@ +export default () => null; diff --git a/src/pages/home/report/ReportActionContextMenu.js b/src/pages/home/report/ContextMenu/ReportActionContextMenu.js similarity index 76% rename from src/pages/home/report/ReportActionContextMenu.js rename to src/pages/home/report/ContextMenu/ReportActionContextMenu.js index ca8fcc35b04f0..46d3c57204000 100755 --- a/src/pages/home/report/ReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/ReportActionContextMenu.js @@ -6,55 +6,30 @@ import lodashGet from 'lodash/get'; import Str from 'expensify-common/lib/str'; import { Clipboard as ClipboardIcon, LinkCopy, Mail, Pencil, Trashcan, Checkmark, -} from '../../../components/Icon/Expensicons'; -import getReportActionContextMenuStyles from '../../../styles/getReportActionContextMenuStyles'; +} from '../../../../components/Icon/Expensicons'; +import getReportActionContextMenuStyles from '../../../../styles/getReportActionContextMenuStyles'; import { setNewMarkerPosition, updateLastReadActionID, saveReportActionDraft, -} from '../../../libs/actions/Report'; -import ContextMenuItem from '../../../components/ContextMenuItem'; -import ReportActionPropTypes from './ReportActionPropTypes'; -import Clipboard from '../../../libs/Clipboard'; -import compose from '../../../libs/compose'; -import {isReportMessageAttachment, canEditReportAction, canDeleteReportAction} from '../../../libs/reportUtils'; -import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; -import ReportActionComposeFocusManager from '../../../libs/ReportActionComposeFocusManager'; +} from '../../../../libs/actions/Report'; +import ContextMenuItem from '../../../../components/ContextMenuItem'; +import Clipboard from '../../../../libs/Clipboard'; +import compose from '../../../../libs/compose'; +import { + propTypes as MiniReportActionContextMenuPropsTypes, + defaultProps as MiniReportActionContextMenuDefaultProps, +} from './MiniReportActionContextMenu/MiniReportActionContextMenuPropsTypes'; +import {isReportMessageAttachment, canEditReportAction, canDeleteReportAction} from '../../../../libs/reportUtils'; +import withLocalize from '../../../../components/withLocalize'; +import ReportActionComposeFocusManager from '../../../../libs/ReportActionComposeFocusManager'; const propTypes = { - /** The ID of the report this report action is attached to. */ - // eslint-disable-next-line react/no-unused-prop-types - reportID: PropTypes.number.isRequired, - - /** The report action this context menu is attached to. */ - reportAction: PropTypes.shape(ReportActionPropTypes).isRequired, - - /** If true, this component will be a small, row-oriented menu that displays icons but not text. - If false, this component will be a larger, column-oriented menu that displays icons alongside text in each row. */ - isMini: PropTypes.bool, - - /** Controls the visibility of this component. */ - isVisible: PropTypes.bool, - - /** The copy selection of text. */ - selection: PropTypes.string, - - /** Draft message - if this is set the comment is in 'edit' mode */ - draftMessage: PropTypes.string, + ...MiniReportActionContextMenuPropsTypes, /** Function to dismiss the popover containing this menu */ hidePopover: PropTypes.func.isRequired, - - /** Function to show the delete Action confirmation modal */ - showDeleteConfirmModal: PropTypes.func.isRequired, - - ...withLocalizePropTypes, }; -const defaultProps = { - isMini: false, - isVisible: false, - selection: '', - draftMessage: '', -}; +const defaultProps = MiniReportActionContextMenuDefaultProps; class ReportActionContextMenu extends React.Component { constructor(props) { @@ -146,8 +121,6 @@ class ReportActionContextMenu extends React.Component { }, }, ]; - - this.wrapperStyle = getReportActionContextMenuStyles(this.props.isMini); } /** @@ -177,14 +150,13 @@ class ReportActionContextMenu extends React.Component { render() { return this.props.isVisible && ( - + {this.contextActions.map(contextAction => _.result(contextAction, 'shouldShow', false) && ( contextAction.onPress(this.props.reportAction)} /> diff --git a/src/pages/home/report/ContextMenu/ReportContextMenuContext.js b/src/pages/home/report/ContextMenu/ReportContextMenuContext.js new file mode 100644 index 0000000000000..7ad4ec561b7c3 --- /dev/null +++ b/src/pages/home/report/ContextMenu/ReportContextMenuContext.js @@ -0,0 +1,5 @@ +import React from 'react'; + +const ReportContextMenuContext = React.createContext({}); + +export default ReportContextMenuContext; diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 2561f83b111ce..c5369acb4dd02 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -15,7 +15,7 @@ import Hoverable from '../../../components/Hoverable'; import PopoverWithMeasuredContent from '../../../components/PopoverWithMeasuredContent'; import ReportActionItemSingle from './ReportActionItemSingle'; import ReportActionItemGrouped from './ReportActionItemGrouped'; -import ReportActionContextMenu from './ReportActionContextMenu'; +import ReportActionContextMenu from './ContextMenu/ReportActionContextMenu'; import IOUAction from '../../../components/ReportActionItem/IOUAction'; import ReportActionItemMessage from './ReportActionItemMessage'; import UnreadActionIndicator from '../../../components/UnreadActionIndicator'; @@ -27,6 +27,7 @@ import {deleteReportComment} from '../../../libs/actions/Report'; import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions'; import ControlSelection from '../../../libs/ControlSelection'; import canUseTouchScreen from '../../../libs/canUseTouchscreen'; +import MiniReportActionContextMenu from './ContextMenu/MiniReportActionContextMenu'; const propTypes = { /** The ID of the report this action is on. */ @@ -68,7 +69,6 @@ class ReportActionItem extends Component { constructor(props) { super(props); - this.onPopoverHide = () => {}; this.state = { isPopoverVisible: false, isDeleteCommentConfirmModalVisible: false, @@ -86,18 +86,6 @@ class ReportActionItem extends Component { this.popoverAnchor = undefined; this.showPopover = this.showPopover.bind(this); - this.hidePopover = this.hidePopover.bind(this); - this.measureContent = this.measureContent.bind(this); - this.selection = ''; - this.measureContextMenuAnchorPosition = this.measureContextMenuAnchorPosition.bind(this); - this.confirmDeleteAndHideModal = this.confirmDeleteAndHideModal.bind(this); - this.hideDeleteConfirmModal = this.hideDeleteConfirmModal.bind(this); - this.showDeleteConfirmModal = this.showDeleteConfirmModal.bind(this); - this.contextMenuHide = this.contextMenuHide.bind(this); - } - - componentDidMount() { - Dimensions.addEventListener('change', this.measureContextMenuAnchorPosition); } shouldComponentUpdate(nextProps, nextState) { @@ -112,72 +100,6 @@ class ReportActionItem extends Component { || !_.isEqual(this.props.action, nextProps.action); } - componentWillUnmount() { - Dimensions.removeEventListener('change', this.measureContextMenuAnchorPosition); - } - - /** - * Get the Context menu anchor position - * We calculate the achor coordinates from measureInWindow async method - * - * @returns {Promise} - * @memberof ReportActionItem - */ - getMeasureLocation() { - return new Promise((res) => { - if (this.popoverAnchor) { - this.popoverAnchor.measureInWindow((x, y) => res({x, y})); - } else { - res({x: 0, y: 0}); - } - }); - } - - /** - * Save the location of a native press event & set the Initial Context menu anchor coordinates - * - * @param {Object} nativeEvent - * @returns {Promise} - */ - capturePressLocation(nativeEvent) { - return this.getMeasureLocation().then(({x, y}) => { - this.setState({ - cursorPosition: { - horizontal: nativeEvent.pageX - x, - vertical: nativeEvent.pageY - y, - }, - popoverAnchorPosition: { - horizontal: nativeEvent.pageX, - vertical: nativeEvent.pageY, - }, - }); - }); - } - - contextMenuHide() { - this.onPopoverHide(); - - // After we have called the action, reset it. - this.onPopoverHide = () => {}; - } - - /** - * This gets called on Dimensions change to find the anchor coordinates for the action context menu. - */ - measureContextMenuAnchorPosition() { - if (!this.state.isPopoverVisible) { - return; - } - this.getMeasureLocation().then(({x, y}) => { - this.setState(prev => ({ - popoverAnchorPosition: { - horizontal: prev.cursorPosition.horizontal + x, - vertical: prev.cursorPosition.vertical + y, - }, - })); - }); - } - /** * Show the ReportActionContextMenu modal popover. * @@ -189,59 +111,7 @@ class ReportActionItem extends Component { if (this.props.draftMessage) { return; } - const nativeEvent = event.nativeEvent || {}; - this.selection = selection; - this.capturePressLocation(nativeEvent).then(() => { - this.setState({isPopoverVisible: true}); - }); - } - - /** - * Hide the ReportActionContextMenu modal popover. - * @param {Function} onHideCallback Callback to be called after popover is completely hidden - */ - hidePopover(onHideCallback) { - if (_.isFunction(onHideCallback)) { - this.onPopoverHide = onHideCallback; - } - this.setState({isPopoverVisible: false}); - } - - /** - * Used to calculate the Context Menu Dimensions - * - * @returns {JSX} - * @memberof ReportActionItem - */ - measureContent() { - return ( - - ); - } - - confirmDeleteAndHideModal() { - deleteReportComment(this.props.reportID, this.props.action); - this.setState({isDeleteCommentConfirmModalVisible: false}); - } - - hideDeleteConfirmModal() { - this.setState({isDeleteCommentConfirmModalVisible: false}); - } - - /** - * Opens the Confirm delete action modal - * - * @memberof ReportActionItem - */ - showDeleteConfirmModal() { - this.setState({isDeleteCommentConfirmModalVisible: true}); + this.props.showContextMenu(event, selection, this.popoverAnchor, this.props.reportID, this.props.action, this.props.draftMessage); } render() { @@ -267,87 +137,55 @@ class ReportActionItem extends Component { ); } return ( - <> - this.popoverAnchor = el} - onPressIn={() => this.props.isSmallScreenWidth && canUseTouchScreen() && ControlSelection.block()} - onPressOut={() => ControlSelection.unblock()} - onSecondaryInteraction={this.showPopover} - > - - {hovered => ( - - {this.props.shouldDisplayNewIndicator && ( - + this.popoverAnchor = el} + onPressIn={() => this.props.isSmallScreenWidth && canUseTouchScreen() && ControlSelection.block()} + onPressOut={() => ControlSelection.unblock()} + onSecondaryInteraction={this.showPopover} + > + + {hovered => ( + + {this.props.shouldDisplayNewIndicator && ( + + )} + + {!this.props.displayAsGroup + ? ( + + {children} + + ) + : ( + + {children} + )} - > - {!this.props.displayAsGroup - ? ( - - {children} - - ) - : ( - - {children} - - )} - - - - - )} - - - - - - - + + + + + )} + + ); } } diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index f8bea1b7c2d8f..771a9e92fdbdb 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -4,6 +4,7 @@ import { Keyboard, AppState, ActivityIndicator, + Dimensions, } from 'react-native'; import PropTypes from 'prop-types'; import _ from 'underscore'; @@ -17,6 +18,7 @@ import { setNewMarkerPosition, subscribeToReportTypingEvents, unsubscribeFromReportChannel, + deleteReportComment, } from '../../../libs/actions/Report'; import ONYXKEYS from '../../../ONYXKEYS'; import ReportActionItem from './ReportActionItem'; @@ -34,6 +36,9 @@ import withDrawerState, {withDrawerPropTypes} from '../../../components/withDraw import {flatListRef, scrollToBottom} from '../../../libs/ReportScrollManager'; import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; import ReportActionComposeFocusManager from '../../../libs/ReportActionComposeFocusManager'; +import PopoverWithMeasuredContent from '../../../components/PopoverWithMeasuredContent'; +import ReportActionContextMenu from './ContextMenu/ReportActionContextMenu'; +import ConfirmModal from '../../../components/ConfirmModal'; const propTypes = { /** The ID of the report actions will be created for */ @@ -96,14 +101,43 @@ class ReportActionsView extends React.Component { this.state = { isLoadingMoreChats: false, + contextMenu: { + reportID: 0, + reportAction: {}, + isPopoverVisible: false, + reportActionDraftMessage: '', + isDeleteCommentConfirmModalVisible: false, + cursorPosition: { + horizontal: 0, + vertical: 0, + }, + + // The horizontal and vertical position (relative to the screen) where the popover will display. + popoverAnchorPosition: { + horizontal: 0, + vertical: 0, + }, + selection: '', + }, }; - this.updateSortedReportActions(props.reportActions); this.updateMostRecentIOUReportActionNumber(props.reportActions); + + this.onPopoverHide = () => {}; + this.popoverAnchor = undefined; + this.showContextMenu = this.showContextMenu.bind(this); + this.hidePhideContentMenuopover = this.hideContentMenu.bind(this); + this.measureContent = this.measureContent.bind(this); + this.measureContextMenuAnchorPosition = this.measureContextMenuAnchorPosition.bind(this); + this.confirmDeleteAndHideModal = this.confirmDeleteAndHideModal.bind(this); + this.hideDeleteConfirmModal = this.hideDeleteConfirmModal.bind(this); + this.showDeleteConfirmModal = this.showDeleteConfirmModal.bind(this); + this.contextMenuHidden = this.contextMenuHidden.bind(this); } componentDidMount() { AppState.addEventListener('change', this.onVisibilityChange); + Dimensions.removeEventListener('change', this.measureContextMenuAnchorPosition); // If the reportID is not found then we have either not loaded this chat or the user is unable to access it. // We will attempt to fetch it and redirect if still not accessible. @@ -160,7 +194,10 @@ class ReportActionsView extends React.Component { return true; } - return false; + // ContextMenu props check + return this.state.contextMenu.isPopoverVisible !== nextState.contextMenu.isPopoverVisible + || this.state.contextMenu.popoverAnchorPosition !== nextState.contextMenu.popoverAnchorPosition + || this.state.contextMenu.isDeleteCommentConfirmModalVisible !== nextState.contextMenu.isDeleteCommentConfirmModalVisible; } componentDidUpdate(prevProps) { @@ -203,7 +240,7 @@ class ReportActionsView extends React.Component { } AppState.removeEventListener('change', this.onVisibilityChange); - + Dimensions.removeEventListener('change', this.measureContextMenuAnchorPosition); unsubscribeFromReportChannel(this.props.reportID); } @@ -216,6 +253,160 @@ class ReportActionsView extends React.Component { } } + /** + * Get the Context menu anchor position + * We calculate the achor coordinates from measureInWindow async method + * + * @returns {Promise} + * @memberof ReportActionItem + */ + getMeasureLocation() { + return new Promise((res) => { + if (this.popoverAnchor) { + this.popoverAnchor.measureInWindow((x, y) => res({x, y})); + } else { + res({x: 0, y: 0}); + } + }); + } + + /** + * Show the ReportActionContextMenu modal popover. + * + * @param {Object} [event] - A press event. + * @param {string} [selection] - A copy text. + */ + showContextMenu(event, selection, popoverAnchor, reportID, reportAction, draftMessage) { + const nativeEvent = event.nativeEvent || {}; + this.popoverAnchor = popoverAnchor; + this.capturePressLocation(nativeEvent).then(() => { + this.setState({ + contextMenu: { + reportID, + reportAction, + selection, + isPopoverVisible: true, + reportActionDraftMessage: draftMessage, + }, + }); + }); + } + + /** + * This gets called on Dimensions change to find the anchor coordinates for the action context menu. + */ + measureContextMenuAnchorPosition() { + if (!this.state.contextMenu.isPopoverVisible) { + return; + } + this.getMeasureLocation().then(({x, y}) => { + this.setState(prev => ({ + contextMenu: { + popoverAnchorPosition: { + horizontal: prev.cursorPosition.horizontal + x, + vertical: prev.cursorPosition.vertical + y, + }, + }, + })); + }); + } + + contextMenuHidden() { + this.onPopoverHide(); + + // After we have called the action, reset it. + this.onPopoverHide = () => {}; + } + + /** + * Save the location of a native press event & set the Initial Context menu anchor coordinates + * + * @param {Object} nativeEvent + * @returns {Promise} + */ + capturePressLocation(nativeEvent) { + return this.getMeasureLocation().then(({x, y}) => { + this.setState({ + contextMenu: { + cursorPosition: { + horizontal: nativeEvent.pageX - x, + vertical: nativeEvent.pageY - y, + }, + popoverAnchorPosition: { + horizontal: nativeEvent.pageX, + vertical: nativeEvent.pageY, + }, + }, + }); + }); + } + + + /** + * Hide the ReportActionContextMenu modal popover. + * @param {Function} onHideCallback Callback to be called after popover is completely hidden + */ + hideContentMenu(onHideCallback) { + if (_.isFunction(onHideCallback)) { + this.onPopoverHide = onHideCallback; + } + this.setState({ + contextMenu: { + isPopoverVisible: false, + }, + }); + } + + /** + * Used to calculate the Context Menu Dimensions + * + * @returns {JSX} + * @memberof ReportActionItem + */ + measureContent() { + return ( + + ); + } + + confirmDeleteAndHideModal() { + deleteReportComment(this.state.contextMenu.reportID, this.state.contextMenu.reportAction); + this.setState({ + contextMenu: { + isDeleteCommentConfirmModalVisible: false, + }, + }); + } + + hideDeleteConfirmModal() { + this.setState({ + contextMenu: { + isDeleteCommentConfirmModalVisible: false, + }, + }); + } + + /** + * Opens the Confirm delete action modal + * + * @memberof ReportActionItem + */ + showDeleteConfirmModal() { + this.setState({ + contextMenu: { + isDeleteCommentConfirmModalVisible: true, + }, + }); + } + + /** * Retrieves the next set of report actions for the chat once we are nearing the end of what we are currently * displaying. @@ -365,7 +556,7 @@ class ReportActionsView extends React.Component { index, }) { const shouldDisplayNewIndicator = this.props.report.newMarkerSequenceNumber > 0 - && item.action.sequenceNumber === this.props.report.newMarkerSequenceNumber; + && item.action.sequenceNumber === this.props.report.newMarkerSequenceNumber; return ( ); } @@ -397,26 +591,57 @@ class ReportActionsView extends React.Component { } return ( - `${item.action.sequenceNumber}${item.action.clientID}`} - initialRowHeight={32} - onEndReached={this.loadMoreChats} - onEndReachedThreshold={0.75} - ListFooterComponent={this.state.isLoadingMoreChats - ? - : null} - keyboardShouldPersistTaps="handled" - onLayout={this.recordTimeToMeasureItemLayout} - /> + <> + `${item.action.sequenceNumber}${item.action.clientID}`} + initialRowHeight={32} + onEndReached={this.loadMoreChats} + onEndReachedThreshold={0.75} + ListFooterComponent={this.state.isLoadingMoreChats + ? + : null} + keyboardShouldPersistTaps="handled" + onLayout={this.recordTimeToMeasureItemLayout} + /> + + + + + ); } } From fd92bb330e8fb2b7928e329aa7b979998bde0c05 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Fri, 23 Jul 2021 05:02:48 +0530 Subject: [PATCH 06/84] fix: state props --- src/pages/home/report/ReportActionsView.js | 149 +++++++++------------ 1 file changed, 65 insertions(+), 84 deletions(-) diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index 771a9e92fdbdb..53e971e2f9c32 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -101,32 +101,30 @@ class ReportActionsView extends React.Component { this.state = { isLoadingMoreChats: false, - contextMenu: { - reportID: 0, - reportAction: {}, - isPopoverVisible: false, - reportActionDraftMessage: '', - isDeleteCommentConfirmModalVisible: false, - cursorPosition: { - horizontal: 0, - vertical: 0, - }, + reportID: 0, + reportAction: {}, + isPopoverVisible: false, + reportActionDraftMessage: '', + isDeleteCommentConfirmModalVisible: false, + cursorPosition: { + horizontal: 0, + vertical: 0, + }, - // The horizontal and vertical position (relative to the screen) where the popover will display. - popoverAnchorPosition: { - horizontal: 0, - vertical: 0, - }, - selection: '', + // The horizontal and vertical position (relative to the screen) where the popover will display. + popoverAnchorPosition: { + horizontal: 0, + vertical: 0, }, + selection: '', }; this.updateSortedReportActions(props.reportActions); this.updateMostRecentIOUReportActionNumber(props.reportActions); this.onPopoverHide = () => {}; - this.popoverAnchor = undefined; + this.contextMenuAchor = undefined; this.showContextMenu = this.showContextMenu.bind(this); - this.hidePhideContentMenuopover = this.hideContentMenu.bind(this); + this.hideContentMenu = this.hideContentMenu.bind(this); this.measureContent = this.measureContent.bind(this); this.measureContextMenuAnchorPosition = this.measureContextMenuAnchorPosition.bind(this); this.confirmDeleteAndHideModal = this.confirmDeleteAndHideModal.bind(this); @@ -195,9 +193,9 @@ class ReportActionsView extends React.Component { } // ContextMenu props check - return this.state.contextMenu.isPopoverVisible !== nextState.contextMenu.isPopoverVisible - || this.state.contextMenu.popoverAnchorPosition !== nextState.contextMenu.popoverAnchorPosition - || this.state.contextMenu.isDeleteCommentConfirmModalVisible !== nextState.contextMenu.isDeleteCommentConfirmModalVisible; + return this.state.isPopoverVisible !== nextState.isPopoverVisible + || this.state.popoverAnchorPosition !== nextState.popoverAnchorPosition + || this.state.isDeleteCommentConfirmModalVisible !== nextState.isDeleteCommentConfirmModalVisible; } componentDidUpdate(prevProps) { @@ -262,8 +260,8 @@ class ReportActionsView extends React.Component { */ getMeasureLocation() { return new Promise((res) => { - if (this.popoverAnchor) { - this.popoverAnchor.measureInWindow((x, y) => res({x, y})); + if (this.contextMenuAchor) { + this.contextMenuAchor.measureInWindow((x, y) => res({x, y})); } else { res({x: 0, y: 0}); } @@ -275,19 +273,22 @@ class ReportActionsView extends React.Component { * * @param {Object} [event] - A press event. * @param {string} [selection] - A copy text. + * @param {Element} contextMenuAnchor - popoverAnchor + * @param {Number} reportID - Active Report Id + * @param {Object} reportAction - ReportAction for ContextMenu + * @param {String} draftMessage - ReportAction Draftmessage + * @memberof ReportActionsView */ - showContextMenu(event, selection, popoverAnchor, reportID, reportAction, draftMessage) { + showContextMenu(event, selection, contextMenuAnchor, reportID, reportAction, draftMessage) { const nativeEvent = event.nativeEvent || {}; - this.popoverAnchor = popoverAnchor; + this.contextMenuAchor = contextMenuAnchor; this.capturePressLocation(nativeEvent).then(() => { this.setState({ - contextMenu: { - reportID, - reportAction, - selection, - isPopoverVisible: true, - reportActionDraftMessage: draftMessage, - }, + reportID, + reportAction, + selection, + isPopoverVisible: true, + reportActionDraftMessage: draftMessage, }); }); } @@ -296,16 +297,14 @@ class ReportActionsView extends React.Component { * This gets called on Dimensions change to find the anchor coordinates for the action context menu. */ measureContextMenuAnchorPosition() { - if (!this.state.contextMenu.isPopoverVisible) { + if (!this.state.isPopoverVisible) { return; } this.getMeasureLocation().then(({x, y}) => { this.setState(prev => ({ - contextMenu: { - popoverAnchorPosition: { - horizontal: prev.cursorPosition.horizontal + x, - vertical: prev.cursorPosition.vertical + y, - }, + popoverAnchorPosition: { + horizontal: prev.cursorPosition.horizontal + x, + vertical: prev.cursorPosition.vertical + y, }, })); }); @@ -327,15 +326,13 @@ class ReportActionsView extends React.Component { capturePressLocation(nativeEvent) { return this.getMeasureLocation().then(({x, y}) => { this.setState({ - contextMenu: { - cursorPosition: { - horizontal: nativeEvent.pageX - x, - vertical: nativeEvent.pageY - y, - }, - popoverAnchorPosition: { - horizontal: nativeEvent.pageX, - vertical: nativeEvent.pageY, - }, + cursorPosition: { + horizontal: nativeEvent.pageX - x, + vertical: nativeEvent.pageY - y, + }, + popoverAnchorPosition: { + horizontal: nativeEvent.pageX, + vertical: nativeEvent.pageY, }, }); }); @@ -350,11 +347,7 @@ class ReportActionsView extends React.Component { if (_.isFunction(onHideCallback)) { this.onPopoverHide = onHideCallback; } - this.setState({ - contextMenu: { - isPopoverVisible: false, - }, - }); + this.setState({isPopoverVisible: false}); } /** @@ -367,9 +360,9 @@ class ReportActionsView extends React.Component { return ( @@ -377,20 +370,12 @@ class ReportActionsView extends React.Component { } confirmDeleteAndHideModal() { - deleteReportComment(this.state.contextMenu.reportID, this.state.contextMenu.reportAction); - this.setState({ - contextMenu: { - isDeleteCommentConfirmModalVisible: false, - }, - }); + deleteReportComment(this.state.reportID, this.state.reportAction); + this.setState({isDeleteCommentConfirmModalVisible: false}); } hideDeleteConfirmModal() { - this.setState({ - contextMenu: { - isDeleteCommentConfirmModalVisible: false, - }, - }); + this.setState({isDeleteCommentConfirmModalVisible: false}); } /** @@ -399,11 +384,7 @@ class ReportActionsView extends React.Component { * @memberof ReportActionItem */ showDeleteConfirmModal() { - this.setState({ - contextMenu: { - isDeleteCommentConfirmModalVisible: true, - }, - }); + this.setState({isDeleteCommentConfirmModalVisible: true}); } @@ -613,28 +594,28 @@ class ReportActionsView extends React.Component { onLayout={this.recordTimeToMeasureItemLayout} /> Date: Fri, 23 Jul 2021 10:10:17 +0530 Subject: [PATCH 07/84] fix: positioning of menu --- src/components/PopoverWithMeasuredContent.js | 7 +++ .../ContextMenu/ReportActionContextMenu.js | 2 +- src/pages/home/report/ReportActionItem.js | 47 +++++++----------- src/pages/home/report/ReportActionsView.js | 49 ++++++++++--------- 4 files changed, 51 insertions(+), 54 deletions(-) diff --git a/src/components/PopoverWithMeasuredContent.js b/src/components/PopoverWithMeasuredContent.js index bf67ba3a33271..10808e791fdd4 100644 --- a/src/components/PopoverWithMeasuredContent.js +++ b/src/components/PopoverWithMeasuredContent.js @@ -78,6 +78,13 @@ class PopoverWithMeasuredContent extends Component { ) || !_.isEqual(this.state, nextState); } + componentDidUpdate(prevProps) { + if (prevProps.isVisible !== this.props.isVisible) { + // eslint-disable-next-line react/no-did-update-set-state + this.setState({isContentMeasured: false}); + } + } + /** * Measure the size of the popover's content. * diff --git a/src/pages/home/report/ContextMenu/ReportActionContextMenu.js b/src/pages/home/report/ContextMenu/ReportActionContextMenu.js index 46d3c57204000..ddf0f5f5c7040 100755 --- a/src/pages/home/report/ContextMenu/ReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/ReportActionContextMenu.js @@ -31,7 +31,7 @@ const propTypes = { const defaultProps = MiniReportActionContextMenuDefaultProps; -class ReportActionContextMenu extends React.Component { +class ReportActionContextMenu extends React.PureComponent { constructor(props) { super(props); diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index c5369acb4dd02..e90e425822100 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -1,6 +1,6 @@ import _ from 'underscore'; import React, {Component} from 'react'; -import {Dimensions, View} from 'react-native'; +import {View} from 'react-native'; import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx'; import CONST from '../../../CONST'; @@ -12,18 +12,14 @@ import { } from '../../../styles/getReportActionItemStyles'; import PressableWithSecondaryInteraction from '../../../components/PressableWithSecondaryInteraction'; import Hoverable from '../../../components/Hoverable'; -import PopoverWithMeasuredContent from '../../../components/PopoverWithMeasuredContent'; import ReportActionItemSingle from './ReportActionItemSingle'; import ReportActionItemGrouped from './ReportActionItemGrouped'; -import ReportActionContextMenu from './ContextMenu/ReportActionContextMenu'; import IOUAction from '../../../components/ReportActionItem/IOUAction'; import ReportActionItemMessage from './ReportActionItemMessage'; import UnreadActionIndicator from '../../../components/UnreadActionIndicator'; import ReportActionItemMessageEdit from './ReportActionItemMessageEdit'; -import ConfirmModal from '../../../components/ConfirmModal'; import compose from '../../../libs/compose'; import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; -import {deleteReportComment} from '../../../libs/actions/Report'; import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions'; import ControlSelection from '../../../libs/ControlSelection'; import canUseTouchScreen from '../../../libs/canUseTouchscreen'; @@ -52,6 +48,10 @@ const propTypes = { index: PropTypes.number.isRequired, /* Onyx Props */ + showContextMenu: PropTypes.func.isRequired, + hideContextMenu: PropTypes.func.isRequired, + showDeleteConfirmModal: PropTypes.func.isRequired, + isContextMenuActive: PropTypes.bool, /** Draft message - if this is set the comment is in 'edit' mode */ draftMessage: PropTypes.string, @@ -63,36 +63,18 @@ const propTypes = { const defaultProps = { draftMessage: '', hasOutstandingIOU: false, + isContextMenuActive: false, }; class ReportActionItem extends Component { constructor(props) { super(props); - - this.state = { - isPopoverVisible: false, - isDeleteCommentConfirmModalVisible: false, - cursorPosition: { - horizontal: 0, - vertical: 0, - }, - - // The horizontal and vertical position (relative to the screen) where the popover will display. - popoverAnchorPosition: { - horizontal: 0, - vertical: 0, - }, - }; - this.popoverAnchor = undefined; this.showPopover = this.showPopover.bind(this); } - shouldComponentUpdate(nextProps, nextState) { - return this.state.isPopoverVisible !== nextState.isPopoverVisible - || this.state.popoverAnchorPosition !== nextState.popoverAnchorPosition - || this.state.isDeleteCommentConfirmModalVisible !== nextState.isDeleteCommentConfirmModalVisible - || this.props.displayAsGroup !== nextProps.displayAsGroup + shouldComponentUpdate(nextProps) { + return this.props.displayAsGroup !== nextProps.displayAsGroup || this.props.draftMessage !== nextProps.draftMessage || this.props.isMostRecentIOUReportAction !== nextProps.isMostRecentIOUReportAction || this.props.hasOutstandingIOU !== nextProps.hasOutstandingIOU @@ -111,7 +93,14 @@ class ReportActionItem extends Component { if (this.props.draftMessage) { return; } - this.props.showContextMenu(event, selection, this.popoverAnchor, this.props.reportID, this.props.action, this.props.draftMessage); + this.props.showContextMenu( + event, + selection, + this.popoverAnchor, + this.props.reportID, + this.props.action, + this.props.draftMessage, + ); } render() { @@ -152,7 +141,7 @@ class ReportActionItem extends Component { @@ -174,7 +163,7 @@ class ReportActionItem extends Component { reportAction={this.props.action} isVisible={ hovered - && !this.state.isPopoverVisible + && !this.props.isContextMenuActive && !this.props.draftMessage } draftMessage={this.props.draftMessage} diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index 53e971e2f9c32..56db23587cad2 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -268,6 +268,26 @@ class ReportActionsView extends React.Component { }); } + /** + * Save the location of a native press event & set the Initial Context menu anchor coordinates + * + * @param {Object} nativeEvent + * @returns {Promise} + */ + getPressLocation(nativeEvent) { + return this.getMeasureLocation().then(({x, y}) => ({ + cursorPosition: { + horizontal: nativeEvent.pageX - x, + vertical: nativeEvent.pageY - y, + }, + popoverAnchorPosition: { + horizontal: nativeEvent.pageX, + vertical: nativeEvent.pageY, + }, + })); + } + + /** * Show the ReportActionContextMenu modal popover. * @@ -282,8 +302,10 @@ class ReportActionsView extends React.Component { showContextMenu(event, selection, contextMenuAnchor, reportID, reportAction, draftMessage) { const nativeEvent = event.nativeEvent || {}; this.contextMenuAchor = contextMenuAnchor; - this.capturePressLocation(nativeEvent).then(() => { + this.getPressLocation(nativeEvent).then(({cursorPosition, popoverAnchorPosition}) => { this.setState({ + cursorPosition, + popoverAnchorPosition, reportID, reportAction, selection, @@ -317,28 +339,6 @@ class ReportActionsView extends React.Component { this.onPopoverHide = () => {}; } - /** - * Save the location of a native press event & set the Initial Context menu anchor coordinates - * - * @param {Object} nativeEvent - * @returns {Promise} - */ - capturePressLocation(nativeEvent) { - return this.getMeasureLocation().then(({x, y}) => { - this.setState({ - cursorPosition: { - horizontal: nativeEvent.pageX - x, - vertical: nativeEvent.pageY - y, - }, - popoverAnchorPosition: { - horizontal: nativeEvent.pageX, - vertical: nativeEvent.pageY, - }, - }); - }); - } - - /** * Hide the ReportActionContextMenu modal popover. * @param {Function} onHideCallback Callback to be called after popover is completely hidden @@ -548,7 +548,8 @@ class ReportActionsView extends React.Component { hasOutstandingIOU={this.props.report.hasOutstandingIOU} index={index} showContextMenu={this.showContextMenu} - hideContentMenu={this.hideContentMenu} + hideContextMenu={this.hideContentMenu} + isContextMenuActive={this.state.reportAction.reportActionID === item.action.reportActionID} showDeleteConfirmModal={this.showDeleteConfirmModal} /> ); From f9a0332784c7845a8dd5003284398dfb950b58b8 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Fri, 23 Jul 2021 23:31:48 +0530 Subject: [PATCH 08/84] fixed ContextMenu actions --- src/components/PopoverWithMeasuredContent.js | 3 +- .../report/ContextMenu/ContextMenuItems.js | 98 ------------------- .../MiniReportActionContextMenu/index.js | 7 +- .../ContextMenu/ReportActionContextMenu.js | 23 ++--- .../ContextMenu/ReportContextMenuContext.js | 5 - src/pages/home/report/ReportActionsView.js | 55 +++++------ 6 files changed, 36 insertions(+), 155 deletions(-) delete mode 100644 src/pages/home/report/ContextMenu/ContextMenuItems.js delete mode 100644 src/pages/home/report/ContextMenu/ReportContextMenuContext.js diff --git a/src/components/PopoverWithMeasuredContent.js b/src/components/PopoverWithMeasuredContent.js index 10808e791fdd4..6949859074ef8 100644 --- a/src/components/PopoverWithMeasuredContent.js +++ b/src/components/PopoverWithMeasuredContent.js @@ -79,7 +79,8 @@ class PopoverWithMeasuredContent extends Component { } componentDidUpdate(prevProps) { - if (prevProps.isVisible !== this.props.isVisible) { + // When Popover is shown recalculate + if (!prevProps.isVisible && this.props.isVisible) { // eslint-disable-next-line react/no-did-update-set-state this.setState({isContentMeasured: false}); } diff --git a/src/pages/home/report/ContextMenu/ContextMenuItems.js b/src/pages/home/report/ContextMenu/ContextMenuItems.js deleted file mode 100644 index db51ca13fe376..0000000000000 --- a/src/pages/home/report/ContextMenu/ContextMenuItems.js +++ /dev/null @@ -1,98 +0,0 @@ -import _ from 'underscore'; -import lodashGet from 'lodash/get'; -import Str from 'expensify-common/lib/str'; -import { - Clipboard as ClipboardIcon, LinkCopy, Mail, Pencil, Trashcan, Checkmark, -} from '../../../../components/Icon/Expensicons'; -import { - setNewMarkerPosition, updateLastReadActionID, saveReportActionDraft, -} from '../../../../libs/actions/Report'; -import Clipboard from '../../../../libs/Clipboard'; -import {isReportMessageAttachment, canEditReportAction, canDeleteReportAction} from '../../../../libs/reportUtils'; -import ReportActionComposeFocusManager from '../../../../libs/ReportActionComposeFocusManager'; - -// A list of all the context actions in this menu. -const contextActions = [ - // Copy to clipboard - { - textTranslationKey: 'contextMenuItem.copyToClipboard', - icon: ClipboardIcon, - successTextTranslationKey: 'contextMenuItem.copied', - successIcon: Checkmark, - shouldShow: true, - - // If return value is true, we switch the `text` and `icon` on - // `ContextMenuItem` with `successText` and `successIcon` which will fallback to - // the `text` and `icon` - onPress: (reportAction, selection, hidePopover) => { - const message = _.last(lodashGet(reportAction, 'message', null)); - const html = lodashGet(message, 'html', ''); - const text = Str.htmlDecode(selection || lodashGet(message, 'text', '')); - const isAttachment = _.has(reportAction, 'isAttachment') - ? reportAction.isAttachment - : isReportMessageAttachment(text); - if (!isAttachment) { - Clipboard.setString(text); - } else { - Clipboard.setString(html); - } - hidePopover(true, ReportActionComposeFocusManager.focus); - }, - }, - - { - textTranslationKey: 'reportActionContextMenu.copyLink', - icon: LinkCopy, - shouldShow: false, - onPress: () => {}, - }, - - { - textTranslationKey: 'reportActionContextMenu.markAsUnread', - icon: Mail, - successIcon: Checkmark, - shouldShow: true, - onPress: (reportID, reportAction) => { - updateLastReadActionID(reportID, reportAction.sequenceNumber); - setNewMarkerPosition(reportID, reportAction.sequenceNumber); - this.hidePopover(true, ReportActionComposeFocusManager.focus); - }, - }, - - { - textTranslationKey: 'reportActionContextMenu.editComment', - icon: Pencil, - shouldShow: reportAction => canEditReportAction(reportAction), - onPress: (reportID, reportAction, draftMessage, isMini, hidePopover) => { - const editAction = () => saveReportActionDraft( - reportID, - reportAction.reportActionID, - _.isEmpty(draftMessage) ? this.getActionText() : '', - ); - - if (isMini) { - // No popover to hide, call editAction immediately - editAction(); - } else { - // Hide popover, then call editAction - hidePopover(false, editAction); - } - }, - }, - { - textTranslationKey: 'reportActionContextMenu.deleteComment', - icon: Trashcan, - shouldShow: reportAction => canDeleteReportAction(reportAction), - onPress: (isMini, showDeleteConfirmModal, hidePopover) => { - if (isMini) { - // No popover to hide, call showDeleteConfirmModal immediately - showDeleteConfirmModal(); - } else { - // Hide popover, then call showDeleteConfirmModal - hidePopover(false, showDeleteConfirmModal); - } - }, - }, -]; - -export default contextActions; diff --git a/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/index.js b/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/index.js index 17d68ca11a899..3148cd5da9d5c 100644 --- a/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/index.js +++ b/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/index.js @@ -13,7 +13,6 @@ import { import ContextMenuItem from '../../../../../components/ContextMenuItem'; import {propTypes, defaultProps} from './MiniReportActionContextMenuPropsTypes'; import Clipboard from '../../../../../libs/Clipboard'; -import compose from '../../../../../libs/compose'; import {isReportMessageAttachment, canEditReportAction, canDeleteReportAction} from '../../../../../libs/reportUtils'; import withLocalize from '../../../../../components/withLocalize'; import ReportActionComposeFocusManager from '../../../../../libs/ReportActionComposeFocusManager'; @@ -89,7 +88,7 @@ class MiniReportActionContextMenu extends React.Component { icon: Trashcan, shouldShow: () => canDeleteReportAction(this.props.reportAction), onPress: () => { - this.props.showDeleteConfirmModal(); + this.props.showDeleteConfirmModal(this.props.reportID, this.props.reportAction); }, }, ]; @@ -127,6 +126,4 @@ class MiniReportActionContextMenu extends React.Component { MiniReportActionContextMenu.propTypes = propTypes; MiniReportActionContextMenu.defaultProps = defaultProps; -export default compose( - withLocalize, -)(MiniReportActionContextMenu); +export default withLocalize(MiniReportActionContextMenu); diff --git a/src/pages/home/report/ContextMenu/ReportActionContextMenu.js b/src/pages/home/report/ContextMenu/ReportActionContextMenu.js index ddf0f5f5c7040..7e88f03bf43b7 100755 --- a/src/pages/home/report/ContextMenu/ReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/ReportActionContextMenu.js @@ -31,7 +31,7 @@ const propTypes = { const defaultProps = MiniReportActionContextMenuDefaultProps; -class ReportActionContextMenu extends React.PureComponent { +class ReportActionContextMenu extends React.Component { constructor(props) { super(props); @@ -97,13 +97,8 @@ class ReportActionContextMenu extends React.PureComponent { _.isEmpty(this.props.draftMessage) ? this.getActionText() : '', ); - if (this.props.isMini) { - // No popover to hide, call editAction immediately - editAction(); - } else { - // Hide popover, then call editAction - this.hidePopover(false, editAction); - } + // Hide popover, then call editAction + this.hidePopover(false, editAction); }, }, { @@ -111,13 +106,11 @@ class ReportActionContextMenu extends React.PureComponent { icon: Trashcan, shouldShow: () => canDeleteReportAction(this.props.reportAction), onPress: () => { - if (this.props.isMini) { - // No popover to hide, call showDeleteConfirmModal immediately - this.props.showDeleteConfirmModal(); - } else { - // Hide popover, then call showDeleteConfirmModal - this.hidePopover(false, this.props.showDeleteConfirmModal); - } + // Hide popover, then call showDeleteConfirmModal + this.hidePopover( + false, + () => this.props.showDeleteConfirmModal(this.props.reportID, this.props.reportAction), + ); }, }, ]; diff --git a/src/pages/home/report/ContextMenu/ReportContextMenuContext.js b/src/pages/home/report/ContextMenu/ReportContextMenuContext.js deleted file mode 100644 index 7ad4ec561b7c3..0000000000000 --- a/src/pages/home/report/ContextMenu/ReportContextMenuContext.js +++ /dev/null @@ -1,5 +0,0 @@ -import React from 'react'; - -const ReportContextMenuContext = React.createContext({}); - -export default ReportContextMenuContext; diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index 56db23587cad2..5c47b76ec61a7 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -120,6 +120,7 @@ class ReportActionsView extends React.Component { }; this.updateSortedReportActions(props.reportActions); this.updateMostRecentIOUReportActionNumber(props.reportActions); + this.keyExtractor = this.keyExtractor.bind(this); this.onPopoverHide = () => {}; this.contextMenuAchor = undefined; @@ -131,6 +132,7 @@ class ReportActionsView extends React.Component { this.hideDeleteConfirmModal = this.hideDeleteConfirmModal.bind(this); this.showDeleteConfirmModal = this.showDeleteConfirmModal.bind(this); this.contextMenuHidden = this.contextMenuHidden.bind(this); + this.getContextMenuMeasuredLocation = this.getContextMenuMeasuredLocation.bind(this); } componentDidMount() { @@ -258,7 +260,7 @@ class ReportActionsView extends React.Component { * @returns {Promise} * @memberof ReportActionItem */ - getMeasureLocation() { + getContextMenuMeasuredLocation() { return new Promise((res) => { if (this.contextMenuAchor) { this.contextMenuAchor.measureInWindow((x, y) => res({x, y})); @@ -268,26 +270,6 @@ class ReportActionsView extends React.Component { }); } - /** - * Save the location of a native press event & set the Initial Context menu anchor coordinates - * - * @param {Object} nativeEvent - * @returns {Promise} - */ - getPressLocation(nativeEvent) { - return this.getMeasureLocation().then(({x, y}) => ({ - cursorPosition: { - horizontal: nativeEvent.pageX - x, - vertical: nativeEvent.pageY - y, - }, - popoverAnchorPosition: { - horizontal: nativeEvent.pageX, - vertical: nativeEvent.pageY, - }, - })); - } - - /** * Show the ReportActionContextMenu modal popover. * @@ -302,10 +284,16 @@ class ReportActionsView extends React.Component { showContextMenu(event, selection, contextMenuAnchor, reportID, reportAction, draftMessage) { const nativeEvent = event.nativeEvent || {}; this.contextMenuAchor = contextMenuAnchor; - this.getPressLocation(nativeEvent).then(({cursorPosition, popoverAnchorPosition}) => { + this.getContextMenuMeasuredLocation().then(({x, y}) => { this.setState({ - cursorPosition, - popoverAnchorPosition, + cursorPosition: { + horizontal: nativeEvent.pageX - x, + vertical: nativeEvent.pageY - y, + }, + popoverAnchorPosition: { + horizontal: nativeEvent.pageX, + vertical: nativeEvent.pageY, + }, reportID, reportAction, selection, @@ -322,7 +310,7 @@ class ReportActionsView extends React.Component { if (!this.state.isPopoverVisible) { return; } - this.getMeasureLocation().then(({x, y}) => { + this.getContextMenuMeasuredLocation().then(({x, y}) => { this.setState(prev => ({ popoverAnchorPosition: { horizontal: prev.cursorPosition.horizontal + x, @@ -350,6 +338,10 @@ class ReportActionsView extends React.Component { this.setState({isPopoverVisible: false}); } + keyExtractor(item) { + return `${item.action.sequenceNumber}${item.action.clientID}`; + } + /** * Used to calculate the Context Menu Dimensions * @@ -380,11 +372,12 @@ class ReportActionsView extends React.Component { /** * Opens the Confirm delete action modal - * - * @memberof ReportActionItem + * @param {Number} reportID + * @param {Object} reportAction + * @memberof ReportActionsView */ - showDeleteConfirmModal() { - this.setState({isDeleteCommentConfirmModalVisible: true}); + showDeleteConfirmModal(reportID, reportAction) { + this.setState({reportID, reportAction, isDeleteCommentConfirmModalVisible: true}); } @@ -579,12 +572,12 @@ class ReportActionsView extends React.Component { data={this.sortedReportActions} renderItem={this.renderItem} CellRendererComponent={this.renderCell} - contentContainerStyle={[styles.chatContentScrollView]} + contentContainerStyle={styles.chatContentScrollView} // We use a combination of sequenceNumber and clientID in case the clientID are the same - which // shouldn't happen, but might be possible in some rare cases. // eslint-disable-next-line react/jsx-props-no-multi-spaces - keyExtractor={item => `${item.action.sequenceNumber}${item.action.clientID}`} + keyExtractor={this.keyExtractor} initialRowHeight={32} onEndReached={this.loadMoreChats} onEndReachedThreshold={0.75} From 2ef41c784aacc4c80f4ae574e868f8976da378b2 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Fri, 23 Jul 2021 23:45:02 +0530 Subject: [PATCH 09/84] finalize the menu --- .../ContextMenu/MiniReportActionContextMenu/index.js | 3 --- .../report/ContextMenu/ReportActionContextMenu.js | 5 +---- src/pages/home/report/ReportActionItem.js | 8 +++++--- src/pages/home/report/ReportActionsView.js | 12 ++++++------ 4 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/index.js b/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/index.js index 3148cd5da9d5c..d31296f9f090b 100644 --- a/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/index.js +++ b/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/index.js @@ -15,7 +15,6 @@ import {propTypes, defaultProps} from './MiniReportActionContextMenuPropsTypes'; import Clipboard from '../../../../../libs/Clipboard'; import {isReportMessageAttachment, canEditReportAction, canDeleteReportAction} from '../../../../../libs/reportUtils'; import withLocalize from '../../../../../components/withLocalize'; -import ReportActionComposeFocusManager from '../../../../../libs/ReportActionComposeFocusManager'; class MiniReportActionContextMenu extends React.Component { constructor(props) { @@ -48,7 +47,6 @@ class MiniReportActionContextMenu extends React.Component { } else { Clipboard.setString(html); } - this.props.hidePopover(true, ReportActionComposeFocusManager.focus); }, }, @@ -67,7 +65,6 @@ class MiniReportActionContextMenu extends React.Component { onPress: () => { updateLastReadActionID(this.props.reportID, this.props.reportAction.sequenceNumber); setNewMarkerPosition(this.props.reportID, this.props.reportAction.sequenceNumber); - this.props.hidePopover(true, ReportActionComposeFocusManager.focus); }, }, diff --git a/src/pages/home/report/ContextMenu/ReportActionContextMenu.js b/src/pages/home/report/ContextMenu/ReportActionContextMenu.js index 7e88f03bf43b7..2f8015f3eb5e4 100755 --- a/src/pages/home/report/ContextMenu/ReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/ReportActionContextMenu.js @@ -13,7 +13,6 @@ import { } from '../../../../libs/actions/Report'; import ContextMenuItem from '../../../../components/ContextMenuItem'; import Clipboard from '../../../../libs/Clipboard'; -import compose from '../../../../libs/compose'; import { propTypes as MiniReportActionContextMenuPropsTypes, defaultProps as MiniReportActionContextMenuDefaultProps, @@ -162,6 +161,4 @@ class ReportActionContextMenu extends React.Component { ReportActionContextMenu.propTypes = propTypes; ReportActionContextMenu.defaultProps = defaultProps; -export default compose( - withLocalize, -)(ReportActionContextMenu); +export default withLocalize(ReportActionContextMenu); diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index e90e425822100..7d9cb7f9ed01b 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -47,10 +47,13 @@ const propTypes = { /** Position index of the report action in the overall report FlatList view */ index: PropTypes.number.isRequired, - /* Onyx Props */ + /** Function to show the Context Menu */ showContextMenu: PropTypes.func.isRequired, - hideContextMenu: PropTypes.func.isRequired, + + /** Function to show the delete Action confirmation modal */ showDeleteConfirmModal: PropTypes.func.isRequired, + + /** Whether the contextMenu is action for this action */ isContextMenuActive: PropTypes.bool, /** Draft message - if this is set the comment is in 'edit' mode */ @@ -167,7 +170,6 @@ class ReportActionItem extends Component { && !this.props.draftMessage } draftMessage={this.props.draftMessage} - hidePopover={this.props.hideContextMenu} showDeleteConfirmModal={this.props.showDeleteConfirmModal} /> diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index 5c47b76ec61a7..bc5ac568b6976 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -125,7 +125,7 @@ class ReportActionsView extends React.Component { this.onPopoverHide = () => {}; this.contextMenuAchor = undefined; this.showContextMenu = this.showContextMenu.bind(this); - this.hideContentMenu = this.hideContentMenu.bind(this); + this.hideContextMenu = this.hideContextMenu.bind(this); this.measureContent = this.measureContent.bind(this); this.measureContextMenuAnchorPosition = this.measureContextMenuAnchorPosition.bind(this); this.confirmDeleteAndHideModal = this.confirmDeleteAndHideModal.bind(this); @@ -331,7 +331,7 @@ class ReportActionsView extends React.Component { * Hide the ReportActionContextMenu modal popover. * @param {Function} onHideCallback Callback to be called after popover is completely hidden */ - hideContentMenu(onHideCallback) { + hideContextMenu(onHideCallback) { if (_.isFunction(onHideCallback)) { this.onPopoverHide = onHideCallback; } @@ -355,7 +355,7 @@ class ReportActionsView extends React.Component { selection={this.state.selection} reportID={this.state.reportID} reportAction={this.state.reportAction} - hidePopover={this.hideContentMenu} + hidePopover={this.hideContextMenu} showDeleteConfirmModal={this.showDeleteConfirmModal} /> ); @@ -541,7 +541,7 @@ class ReportActionsView extends React.Component { hasOutstandingIOU={this.props.report.hasOutstandingIOU} index={index} showContextMenu={this.showContextMenu} - hideContextMenu={this.hideContentMenu} + hideContextMenu={this.hideContextMenu} isContextMenuActive={this.state.reportAction.reportActionID === item.action.reportActionID} showDeleteConfirmModal={this.showDeleteConfirmModal} /> @@ -589,7 +589,7 @@ class ReportActionsView extends React.Component { /> From 99b617eece7aac263ec0b3418a9871352bef49fc Mon Sep 17 00:00:00 2001 From: Ali Abbas Malik Date: Fri, 23 Jul 2021 20:27:36 -0400 Subject: [PATCH 10/84] [IS-3599] Fixed crash issue when editing a message --- src/pages/home/report/ReportActionItemMessageEdit.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index ba9521ff9fbe5..10b1c94214322 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -94,7 +94,8 @@ class ReportActionItemMessageEdit extends React.Component { * the new content. */ publishDraft() { - editReportComment(this.props.reportID, this.props.action, this.state.draft); + const trimmedNewDraft = this.state.draft.trim(); + editReportComment(this.props.reportID, this.props.action, trimmedNewDraft); this.deleteDraft(); } From 673a57f7a24bbc7467376a2cf22c44058ab662c4 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Sun, 25 Jul 2021 18:41:31 +0530 Subject: [PATCH 11/84] added Balance badge to the Settings page --- src/components/Badge.js | 63 ++++++++++++++++++++++++++++++ src/components/EnvironmentBadge.js | 18 +++------ src/components/IOUBadge.js | 32 ++++++--------- src/components/MenuItem.js | 7 ++++ src/pages/settings/InitialPage.js | 19 +++++++++ src/styles/styles.js | 20 ++++++---- 6 files changed, 118 insertions(+), 41 deletions(-) create mode 100644 src/components/Badge.js diff --git a/src/components/Badge.js b/src/components/Badge.js new file mode 100644 index 0000000000000..0f0aa55b56401 --- /dev/null +++ b/src/components/Badge.js @@ -0,0 +1,63 @@ +import React from 'react'; +import {Pressable, View} from 'react-native'; +import PropTypes from 'prop-types'; +import styles, {getBadgeColorStyle} from '../styles/styles'; +import Text from './Text'; + +const propTypes = { + /** Is success type */ + success: PropTypes.bool, + + /** Is success type */ + error: PropTypes.bool, + + /** Whether badge is clickable */ + pressable: PropTypes.bool, + + /** Text to display in the Badge */ + text: PropTypes.string.isRequired, + + /** Styles for Badge */ + badgeStyles: PropTypes.arrayOf(PropTypes.object), + + /** Callback to be called on onPress */ + onPress: PropTypes.func, +}; + +const defaultProps = { + success: false, + error: false, + pressable: false, + badgeStyles: [], + onPress: undefined, +}; + +const Badge = (props) => { + const textStyles = props.success || props.error ? styles.textWhite : undefined; + const Wrapper = props.pressable ? Pressable : View; + const wrapperStyles = ({pressed}) => ([ + styles.badge, + styles.ml2, + getBadgeColorStyle(props.success, props.error, pressed), + ...props.badgeStyles, + ]); + + return ( + + + {props.text} + + + ); +}; + +Badge.displayName = 'EnvironmentBadge'; +Badge.propTypes = propTypes; +Badge.defaultProps = defaultProps; +export default Badge; diff --git a/src/components/EnvironmentBadge.js b/src/components/EnvironmentBadge.js index f7bde09733a56..122a0e47d87a4 100644 --- a/src/components/EnvironmentBadge.js +++ b/src/components/EnvironmentBadge.js @@ -1,9 +1,7 @@ import React from 'react'; -import {View} from 'react-native'; -import styles from '../styles/styles'; import CONST from '../CONST'; -import Text from './Text'; import withEnvironment, {environmentPropTypes} from './withEnvironment'; +import Badge from './Badge'; const EnvironmentBadge = (props) => { // If we are on production, don't show any badge @@ -11,16 +9,12 @@ const EnvironmentBadge = (props) => { return null; } - const backgroundColorStyle = props.environment === CONST.ENVIRONMENT.STAGING - ? styles.badgeSuccess - : styles.badgeDanger; - return ( - - - {props.environment} - - + ); }; diff --git a/src/components/IOUBadge.js b/src/components/IOUBadge.js index ebc9852d54019..2eb516b427293 100644 --- a/src/components/IOUBadge.js +++ b/src/components/IOUBadge.js @@ -1,15 +1,13 @@ import React from 'react'; import PropTypes from 'prop-types'; -import {Pressable} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import ONYXKEYS from '../ONYXKEYS'; -import styles, {getBadgeColorStyle} from '../styles/styles'; import Navigation from '../libs/Navigation/Navigation'; import ROUTES from '../ROUTES'; import compose from '../libs/compose'; import withLocalize, {withLocalizePropTypes} from './withLocalize'; import CONST from '../CONST'; -import Text from './Text'; +import Badge from './Badge'; const propTypes = { /** IOU Report data object */ @@ -53,24 +51,16 @@ const IOUBadge = (props) => { Navigation.navigate(ROUTES.getIouDetailsRoute(props.iouReport.chatReportID, props.iouReport.reportID)); }; return ( - ([ - styles.badge, - styles.ml2, - getBadgeColorStyle(props.session.email === props.iouReport.ownerEmail, pressed), - ])} - > - - {props.numberFormat( - props.iouReport.total / 100, - {style: 'currency', currency: props.iouReport.currency}, - )} - - + ); }; diff --git a/src/components/MenuItem.js b/src/components/MenuItem.js index 1a28310396ed4..36bc13ba18066 100644 --- a/src/components/MenuItem.js +++ b/src/components/MenuItem.js @@ -8,8 +8,12 @@ import styles, {getButtonBackgroundColorStyle, getIconFillColor} from '../styles import Icon from './Icon'; import {ArrowRight} from './Icon/Expensicons'; import getButtonState from '../libs/getButtonState'; +import Badge from './Badge'; const propTypes = { + // Text to be shown as badge near the right end. + badgeText: PropTypes.string, + /** Any additional styles to apply */ // eslint-disable-next-line react/forbid-prop-types wrapperStyle: PropTypes.object, @@ -58,6 +62,7 @@ const propTypes = { }; const defaultProps = { + badgeText: undefined, shouldShowRightIcon: false, wrapperStyle: {}, success: false, @@ -74,6 +79,7 @@ const defaultProps = { }; const MenuItem = ({ + badgeText, onPress, icon, iconRight, @@ -141,6 +147,7 @@ const MenuItem = ({ + {badgeText && } {subtitle && ( { + const walletBalance = numberFormat( + userWallet.availableBalance, + {style: 'currency', currency: 'USD'}, + ); + // On the very first sign in or after clearing storage these // details will not be present on the first render so we'll just // return nothing for now. @@ -169,6 +183,7 @@ const InitialSettingsPage = ({ {_.map(menuItems, (item, index) => { const keyTitle = item.translationKey ? translate(item.translationKey) : item.title; + const isPaymentItem = item.translationKey === 'common.payments'; return ( ); })} @@ -206,5 +222,8 @@ export default compose( policies: { key: ONYXKEYS.COLLECTION.POLICY, }, + userWallet: { + key: ONYXKEYS.USER_WALLET, + }, }), )(InitialSettingsPage); diff --git a/src/styles/styles.js b/src/styles/styles.js index c765784a5a932..9aeb38318f68d 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -366,7 +366,7 @@ const styles = { }, badgeText: { - color: themeColors.textReversed, + color: themeColors.text, fontSize: variables.fontSizeSmall, lineHeight: 16, ...whiteSpace.noWrap, @@ -2012,17 +2012,21 @@ function getBackgroundColorStyle(backgroundColor) { } /** - * Generate a style for the background color of the IOU badge + * Generate a style for the background color of the Badge * - * @param {Boolean} isOwner - * @param {Boolean} [isPressed] - * @returns {Object} + * @param {*} success + * @param {*} error + * @param {boolean} [isPressed=false] + * @return {Object} */ -function getBadgeColorStyle(isOwner, isPressed = false) { - if (isOwner) { +function getBadgeColorStyle(success, error, isPressed = false) { + if (success) { return isPressed ? styles.badgeSuccessPressed : styles.badgeSuccess; } - return isPressed ? styles.badgeDangerPressed : styles.badgeDanger; + if (error) { + return isPressed ? styles.badgeDangerPressed : styles.badgeDanger; + } + return {}; } /** From 915649e485da68590fe82d91e33f79489f746316 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Sun, 25 Jul 2021 19:30:36 +0530 Subject: [PATCH 12/84] fix: dev stuff --- src/components/Badge.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Badge.js b/src/components/Badge.js index 0f0aa55b56401..26ee47648c694 100644 --- a/src/components/Badge.js +++ b/src/components/Badge.js @@ -57,7 +57,7 @@ const Badge = (props) => { ); }; -Badge.displayName = 'EnvironmentBadge'; +Badge.displayName = 'Badge'; Badge.propTypes = propTypes; Badge.defaultProps = defaultProps; export default Badge; From 68719dafdce405a939799e8fd8be0ec103428fe3 Mon Sep 17 00:00:00 2001 From: Aman Ansari Date: Sun, 25 Jul 2021 22:53:11 +0530 Subject: [PATCH 13/84] use translate() to properly format date-time --- src/libs/DateUtils.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/libs/DateUtils.js b/src/libs/DateUtils.js index 7b9e77d9c9196..b2107c8213101 100644 --- a/src/libs/DateUtils.js +++ b/src/libs/DateUtils.js @@ -7,6 +7,7 @@ import _ from 'underscore'; import Onyx from 'react-native-onyx'; import ONYXKEYS from '../ONYXKEYS'; import CONST from '../CONST'; +import {translate} from './translate'; let timezone; Onyx.connect({ @@ -57,13 +58,18 @@ function timestampToDateTime(locale, timestamp, includeTimeZone = false) { const date = getLocalMomentFromTimestamp(locale, timestamp); const tz = includeTimeZone ? ' [UTC]Z' : ''; + const todayAt = translate(locale, 'common.todayAt'); + const tomorrowAt = translate(locale, 'common.tomorrowAt'); + const yesterdayAt = translate(locale, 'common.yesterdayAt'); + const at = translate(locale, 'common.conjunctionAt'); + return moment(date).calendar({ - sameDay: `[Today at] LT${tz}`, - nextDay: `[Tomorrow at] LT${tz}`, - nextWeek: `MMM D [at] LT${tz}`, - lastDay: `[Yesterday at] LT${tz}`, - lastWeek: `MMM D [at] LT${tz}`, - sameElse: `MMM D, YYYY [at] LT${tz}`, + sameDay: `[${todayAt}] LT${tz}`, + nextDay: `[${tomorrowAt}] LT${tz}`, + lastDay: `[${yesterdayAt}] LT${tz}`, + nextWeek: `MMM D [${at}] LT${tz}`, + lastWeek: `MMM D [${at}] LT${tz}`, + sameElse: `MMM D, YYYY [${at}] LT${tz}`, }); } From 89ccc6f520257ba516cab649f31449172b7a4379 Mon Sep 17 00:00:00 2001 From: Aman Ansari Date: Sun, 25 Jul 2021 23:26:43 +0530 Subject: [PATCH 14/84] add & fix some locales --- src/languages/en.js | 5 +++++ src/languages/es.js | 13 ++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/languages/en.js b/src/languages/en.js index 0d38c1bcf1025..9f5692ec20c8d 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -60,6 +60,11 @@ export default { noResultsFound: 'No results found', timePrefix: 'It\'s', conjunctionFor: 'for', + todayAt: 'Today at', + tomorrowAt: 'Tomorrow at', + yesterdayAt: 'Yesterday at', + conjunctionAt: 'at', + }, attachmentPicker: { cameraPermissionRequired: 'Camera Permission Required', diff --git a/src/languages/es.js b/src/languages/es.js index 6742589880661..cc44a44a96eb6 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -21,6 +21,8 @@ export default { not: 'No', signIn: 'Conectarse', continue: 'Continuar', + firstName: 'Nombre', + lastName: 'Apellidos', phoneNumber: 'Número de teléfono', email: 'Email', and: 'y', @@ -55,6 +57,11 @@ export default { noResultsFound: 'No se han encontrado resultados', timePrefix: 'Son las', conjunctionFor: 'para', + todayAt: 'Hoy a las', + tomorrowAt: 'Mañana a las', + yesterdayAt: 'Ayer a las', + conjunctionAt: 'a', + }, attachmentPicker: { cameraPermissionRequired: 'Se necesita permiso para usar la cámara', @@ -105,9 +112,11 @@ export default { youAppearToBeOffline: 'Parece que estás desconectado.', roomIsArchived: 'Esta sala de chat ha sido eliminada', }, - reportActionContextMenu: { + contextMenuItem: { copyToClipboard: 'Copiar al Portapapeles', copied: '¡Copiado!', + }, + reportActionContextMenu: { copyLink: 'Copiar Enlace', markAsUnread: 'Marcar como no leído', editComment: 'Editar Commentario', @@ -170,9 +179,7 @@ export default { profilePage: { profile: 'Perfil', tellUsAboutYourself: '¡Cuéntanos algo sobre tí, nos encantaría conocerte!', - firstName: 'Nombre', john: 'Juan', - lastName: 'Apellidos', doe: 'Nadie', preferredPronouns: 'Pronombres preferidos', selectYourPronouns: 'Selecciona tus pronombres', From 8e8e221894038b31ccb5c59628dea7930d1db7c3 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Mon, 26 Jul 2021 01:56:43 +0530 Subject: [PATCH 15/84] Optimizations for Conextmenu --- .../report/ContextMenu/ContextMenuContext.js | 12 + .../PopoverReportActionContextMenu.js | 251 ++++++++++++++++++ src/pages/home/report/ReportActionItem.js | 21 +- src/pages/home/report/ReportActionsView.js | 204 +------------- 4 files changed, 279 insertions(+), 209 deletions(-) create mode 100644 src/pages/home/report/ContextMenu/ContextMenuContext.js create mode 100644 src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js diff --git a/src/pages/home/report/ContextMenu/ContextMenuContext.js b/src/pages/home/report/ContextMenu/ContextMenuContext.js new file mode 100644 index 0000000000000..700fe6458a5b1 --- /dev/null +++ b/src/pages/home/report/ContextMenu/ContextMenuContext.js @@ -0,0 +1,12 @@ +import React from 'react'; + +const ContextMenuContextDefaultValue = { + showContextMenu: () => {}, + hideContextMenu: () => {}, + isActionReportAction: () => {}, + showDeleteConfirmModal: () => {}, +}; +const ContextMenuContext = React.createContext(ContextMenuContextDefaultValue); +ContextMenuContext.displayName = 'ContextMenuContext'; + +export {ContextMenuContext, ContextMenuContextDefaultValue}; diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js new file mode 100644 index 0000000000000..637b418727b61 --- /dev/null +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js @@ -0,0 +1,251 @@ +import React from 'react'; +import { + Dimensions, +} from 'react-native'; +import PropTypes from 'prop-types'; +import _ from 'underscore'; +import { + deleteReportComment, +} from '../../../../libs/actions/Report'; +import withLocalize, {withLocalizePropTypes} from '../../../../components/withLocalize'; +import PopoverWithMeasuredContent from '../../../../components/PopoverWithMeasuredContent'; +import ReportActionContextMenu from './ReportActionContextMenu'; +import ConfirmModal from '../../../../components/ConfirmModal'; + +const propTypes = { + /** The report currently being looked at */ + setValue: PropTypes.func.isRequired, + ...withLocalizePropTypes, +}; + +const defaultProps = { +}; + +class PopoverReportActionContextMenu extends React.Component { + constructor(props) { + super(props); + + this.state = { + reportID: 0, + reportAction: {}, + isPopoverVisible: false, + reportActionDraftMessage: '', + isDeleteCommentConfirmModalVisible: false, + cursorRelativePosition: { + horizontal: 0, + vertical: 0, + }, + + // The horizontal and vertical position (relative to the screen) where the popover will display. + popoverAnchorPosition: { + horizontal: 0, + vertical: 0, + }, + selection: '', + }; + this.onPopoverHide = () => {}; + this.contextMenuAchor = undefined; + this.showContextMenu = this.showContextMenu.bind(this); + this.hideContextMenu = this.hideContextMenu.bind(this); + this.measureContent = this.measureContent.bind(this); + this.measureContextMenuAnchorPosition = this.measureContextMenuAnchorPosition.bind(this); + this.confirmDeleteAndHideModal = this.confirmDeleteAndHideModal.bind(this); + this.hideDeleteConfirmModal = this.hideDeleteConfirmModal.bind(this); + this.showDeleteConfirmModal = this.showDeleteConfirmModal.bind(this); + this.contextMenuHidden = this.contextMenuHidden.bind(this); + this.getContextMenuMeasuredLocation = this.getContextMenuMeasuredLocation.bind(this); + this.isActionReportAction = this.isActionReportAction.bind(this); + } + + componentDidMount() { + this.props.setValue({ + showContextMenu: this.showContextMenu, + hideContextMenu: this.hideContextMenu, + isActionReportAction: this.isActionReportAction, + showDeleteConfirmModal: this.showDeleteConfirmModal, + }); + Dimensions.addEventListener('change', this.measureContextMenuAnchorPosition); + } + + shouldComponentUpdate(nextProps, nextState) { + return this.state.isPopoverVisible !== nextState.isPopoverVisible + || this.state.popoverAnchorPosition !== nextState.popoverAnchorPosition + || this.state.isDeleteCommentConfirmModalVisible !== nextState.isDeleteCommentConfirmModalVisible; + } + + componentWillUnmount() { + Dimensions.removeEventListener('change', this.measureContextMenuAnchorPosition); + } + + /** + * Get the Context menu anchor position + * We calculate the achor coordinates from measureInWindow async method + * + * @returns {Promise} + * @memberof ReportActionItem + */ + getContextMenuMeasuredLocation() { + return new Promise((res) => { + if (this.contextMenuAchor) { + this.contextMenuAchor.measureInWindow((x, y) => res({x, y})); + } else { + res({x: 0, y: 0}); + } + }); + } + + isActionReportAction(actionId) { + return this.state.reportAction.reportActionID === actionId; + } + + /** + * Show the ReportActionContextMenu modal popover. + * + * @param {Object} [event] - A press event. + * @param {string} [selection] - A copy text. + * @param {Element} contextMenuAnchor - popoverAnchor + * @param {Number} reportID - Active Report Id + * @param {Object} reportAction - ReportAction for ContextMenu + * @param {String} draftMessage - ReportAction Draftmessage + * @memberof ReportActionsView + */ + showContextMenu(event, selection, contextMenuAnchor, reportID, reportAction, draftMessage) { + const nativeEvent = event.nativeEvent || {}; + this.contextMenuAchor = contextMenuAnchor; + this.getContextMenuMeasuredLocation().then(({x, y}) => { + this.setState({ + cursorRelativePosition: { + horizontal: nativeEvent.pageX - x, + vertical: nativeEvent.pageY - y, + }, + popoverAnchorPosition: { + horizontal: nativeEvent.pageX, + vertical: nativeEvent.pageY, + }, + reportID, + reportAction, + selection, + isPopoverVisible: true, + reportActionDraftMessage: draftMessage, + }); + }); + } + + /** + * This gets called on Dimensions change to find the anchor coordinates for the action context menu. + */ + measureContextMenuAnchorPosition() { + if (!this.state.isPopoverVisible) { + return; + } + this.getContextMenuMeasuredLocation().then(({x, y}) => { + if (!x || !y) { + console.debug('render skipped'); + return; + } + this.setState(prev => ({ + popoverAnchorPosition: { + horizontal: prev.cursorRelativePosition.horizontal + x, + vertical: prev.cursorRelativePosition.vertical + y, + }, + })); + }); + } + + contextMenuHidden() { + this.onPopoverHide(); + + // After we have called the action, reset it. + this.onPopoverHide = () => {}; + } + + /** + * Hide the ReportActionContextMenu modal popover. + * @param {Function} onHideCallback Callback to be called after popover is completely hidden + */ + hideContextMenu(onHideCallback) { + if (_.isFunction(onHideCallback)) { + this.onPopoverHide = onHideCallback; + } + this.setState({isPopoverVisible: false}); + } + + /** + * Used to calculate the Context Menu Dimensions + * + * @returns {JSX} + * @memberof ReportActionItem + */ + measureContent() { + return ( + + ); + } + + confirmDeleteAndHideModal() { + deleteReportComment(this.state.reportID, this.state.reportAction); + this.setState({isDeleteCommentConfirmModalVisible: false}); + } + + hideDeleteConfirmModal() { + this.setState({isDeleteCommentConfirmModalVisible: false}); + } + + /** + * Opens the Confirm delete action modal + * @param {Number} reportID + * @param {Object} reportAction + * @memberof ReportActionsView + */ + showDeleteConfirmModal(reportID, reportAction) { + this.setState({reportID, reportAction, isDeleteCommentConfirmModalVisible: true}); + } + + render() { + return ( + <> + + + + + + ); + } +} + +PopoverReportActionContextMenu.propTypes = propTypes; +PopoverReportActionContextMenu.defaultProps = defaultProps; + +export default withLocalize(PopoverReportActionContextMenu); diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 7d9cb7f9ed01b..5a7c8abe2368f 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -24,6 +24,7 @@ import withWindowDimensions, {windowDimensionsPropTypes} from '../../../componen import ControlSelection from '../../../libs/ControlSelection'; import canUseTouchScreen from '../../../libs/canUseTouchscreen'; import MiniReportActionContextMenu from './ContextMenu/MiniReportActionContextMenu'; +import {ContextMenuContext} from './ContextMenu/ContextMenuContext'; const propTypes = { /** The ID of the report this action is on. */ @@ -47,15 +48,6 @@ const propTypes = { /** Position index of the report action in the overall report FlatList view */ index: PropTypes.number.isRequired, - /** Function to show the Context Menu */ - showContextMenu: PropTypes.func.isRequired, - - /** Function to show the delete Action confirmation modal */ - showDeleteConfirmModal: PropTypes.func.isRequired, - - /** Whether the contextMenu is action for this action */ - isContextMenuActive: PropTypes.bool, - /** Draft message - if this is set the comment is in 'edit' mode */ draftMessage: PropTypes.string, @@ -66,7 +58,6 @@ const propTypes = { const defaultProps = { draftMessage: '', hasOutstandingIOU: false, - isContextMenuActive: false, }; class ReportActionItem extends Component { @@ -96,7 +87,7 @@ class ReportActionItem extends Component { if (this.props.draftMessage) { return; } - this.props.showContextMenu( + this.context.showContextMenu( event, selection, this.popoverAnchor, @@ -144,7 +135,7 @@ class ReportActionItem extends Component { @@ -166,11 +157,11 @@ class ReportActionItem extends Component { reportAction={this.props.action} isVisible={ hovered - && !this.props.isContextMenuActive + && !this.context.isActionReportAction(this.props.action.reportActionID) && !this.props.draftMessage } draftMessage={this.props.draftMessage} - showDeleteConfirmModal={this.props.showDeleteConfirmModal} + showDeleteConfirmModal={this.context.showDeleteConfirmModal} /> @@ -180,7 +171,7 @@ class ReportActionItem extends Component { ); } } - +ReportActionItem.contextType = ContextMenuContext; ReportActionItem.propTypes = propTypes; ReportActionItem.defaultProps = defaultProps; diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index bc5ac568b6976..ebd3a3dfcd711 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -4,7 +4,6 @@ import { Keyboard, AppState, ActivityIndicator, - Dimensions, } from 'react-native'; import PropTypes from 'prop-types'; import _ from 'underscore'; @@ -18,7 +17,6 @@ import { setNewMarkerPosition, subscribeToReportTypingEvents, unsubscribeFromReportChannel, - deleteReportComment, } from '../../../libs/actions/Report'; import ONYXKEYS from '../../../ONYXKEYS'; import ReportActionItem from './ReportActionItem'; @@ -36,9 +34,8 @@ import withDrawerState, {withDrawerPropTypes} from '../../../components/withDraw import {flatListRef, scrollToBottom} from '../../../libs/ReportScrollManager'; import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; import ReportActionComposeFocusManager from '../../../libs/ReportActionComposeFocusManager'; -import PopoverWithMeasuredContent from '../../../components/PopoverWithMeasuredContent'; -import ReportActionContextMenu from './ContextMenu/ReportActionContextMenu'; -import ConfirmModal from '../../../components/ConfirmModal'; +import {ContextMenuContext, ContextMenuContextDefaultValue} from './ContextMenu/ContextMenuContext'; +import PopoverReportActionContextMenu from './ContextMenu/PopoverReportActionContextMenu'; const propTypes = { /** The ID of the report actions will be created for */ @@ -101,43 +98,17 @@ class ReportActionsView extends React.Component { this.state = { isLoadingMoreChats: false, - reportID: 0, - reportAction: {}, - isPopoverVisible: false, - reportActionDraftMessage: '', - isDeleteCommentConfirmModalVisible: false, - cursorPosition: { - horizontal: 0, - vertical: 0, - }, - - // The horizontal and vertical position (relative to the screen) where the popover will display. - popoverAnchorPosition: { - horizontal: 0, - vertical: 0, - }, - selection: '', + contextMenuContext: ContextMenuContextDefaultValue, }; this.updateSortedReportActions(props.reportActions); this.updateMostRecentIOUReportActionNumber(props.reportActions); this.keyExtractor = this.keyExtractor.bind(this); - this.onPopoverHide = () => {}; - this.contextMenuAchor = undefined; - this.showContextMenu = this.showContextMenu.bind(this); - this.hideContextMenu = this.hideContextMenu.bind(this); - this.measureContent = this.measureContent.bind(this); - this.measureContextMenuAnchorPosition = this.measureContextMenuAnchorPosition.bind(this); - this.confirmDeleteAndHideModal = this.confirmDeleteAndHideModal.bind(this); - this.hideDeleteConfirmModal = this.hideDeleteConfirmModal.bind(this); - this.showDeleteConfirmModal = this.showDeleteConfirmModal.bind(this); - this.contextMenuHidden = this.contextMenuHidden.bind(this); - this.getContextMenuMeasuredLocation = this.getContextMenuMeasuredLocation.bind(this); + this.updateContextMenuContext = this.updateContextMenuContext.bind(this); } componentDidMount() { AppState.addEventListener('change', this.onVisibilityChange); - Dimensions.removeEventListener('change', this.measureContextMenuAnchorPosition); // If the reportID is not found then we have either not loaded this chat or the user is unable to access it. // We will attempt to fetch it and redirect if still not accessible. @@ -195,9 +166,7 @@ class ReportActionsView extends React.Component { } // ContextMenu props check - return this.state.isPopoverVisible !== nextState.isPopoverVisible - || this.state.popoverAnchorPosition !== nextState.popoverAnchorPosition - || this.state.isDeleteCommentConfirmModalVisible !== nextState.isDeleteCommentConfirmModalVisible; + return this.state.contextMenuContext !== nextState.contextMenuContext; } componentDidUpdate(prevProps) { @@ -240,7 +209,6 @@ class ReportActionsView extends React.Component { } AppState.removeEventListener('change', this.onVisibilityChange); - Dimensions.removeEventListener('change', this.measureContextMenuAnchorPosition); unsubscribeFromReportChannel(this.props.reportID); } @@ -253,134 +221,14 @@ class ReportActionsView extends React.Component { } } - /** - * Get the Context menu anchor position - * We calculate the achor coordinates from measureInWindow async method - * - * @returns {Promise} - * @memberof ReportActionItem - */ - getContextMenuMeasuredLocation() { - return new Promise((res) => { - if (this.contextMenuAchor) { - this.contextMenuAchor.measureInWindow((x, y) => res({x, y})); - } else { - res({x: 0, y: 0}); - } - }); - } - - /** - * Show the ReportActionContextMenu modal popover. - * - * @param {Object} [event] - A press event. - * @param {string} [selection] - A copy text. - * @param {Element} contextMenuAnchor - popoverAnchor - * @param {Number} reportID - Active Report Id - * @param {Object} reportAction - ReportAction for ContextMenu - * @param {String} draftMessage - ReportAction Draftmessage - * @memberof ReportActionsView - */ - showContextMenu(event, selection, contextMenuAnchor, reportID, reportAction, draftMessage) { - const nativeEvent = event.nativeEvent || {}; - this.contextMenuAchor = contextMenuAnchor; - this.getContextMenuMeasuredLocation().then(({x, y}) => { - this.setState({ - cursorPosition: { - horizontal: nativeEvent.pageX - x, - vertical: nativeEvent.pageY - y, - }, - popoverAnchorPosition: { - horizontal: nativeEvent.pageX, - vertical: nativeEvent.pageY, - }, - reportID, - reportAction, - selection, - isPopoverVisible: true, - reportActionDraftMessage: draftMessage, - }); - }); - } - - /** - * This gets called on Dimensions change to find the anchor coordinates for the action context menu. - */ - measureContextMenuAnchorPosition() { - if (!this.state.isPopoverVisible) { - return; - } - this.getContextMenuMeasuredLocation().then(({x, y}) => { - this.setState(prev => ({ - popoverAnchorPosition: { - horizontal: prev.cursorPosition.horizontal + x, - vertical: prev.cursorPosition.vertical + y, - }, - })); - }); - } - - contextMenuHidden() { - this.onPopoverHide(); - - // After we have called the action, reset it. - this.onPopoverHide = () => {}; - } - - /** - * Hide the ReportActionContextMenu modal popover. - * @param {Function} onHideCallback Callback to be called after popover is completely hidden - */ - hideContextMenu(onHideCallback) { - if (_.isFunction(onHideCallback)) { - this.onPopoverHide = onHideCallback; - } - this.setState({isPopoverVisible: false}); + updateContextMenuContext(value) { + this.setState({contextMenuContext: value}); } keyExtractor(item) { return `${item.action.sequenceNumber}${item.action.clientID}`; } - /** - * Used to calculate the Context Menu Dimensions - * - * @returns {JSX} - * @memberof ReportActionItem - */ - measureContent() { - return ( - - ); - } - - confirmDeleteAndHideModal() { - deleteReportComment(this.state.reportID, this.state.reportAction); - this.setState({isDeleteCommentConfirmModalVisible: false}); - } - - hideDeleteConfirmModal() { - this.setState({isDeleteCommentConfirmModalVisible: false}); - } - - /** - * Opens the Confirm delete action modal - * @param {Number} reportID - * @param {Object} reportAction - * @memberof ReportActionsView - */ - showDeleteConfirmModal(reportID, reportAction) { - this.setState({reportID, reportAction, isDeleteCommentConfirmModalVisible: true}); - } - - /** * Retrieves the next set of report actions for the chat once we are nearing the end of what we are currently * displaying. @@ -540,10 +388,6 @@ class ReportActionsView extends React.Component { isMostRecentIOUReportAction={item.action.sequenceNumber === this.mostRecentIOUReportSequenceNumber} hasOutstandingIOU={this.props.report.hasOutstandingIOU} index={index} - showContextMenu={this.showContextMenu} - hideContextMenu={this.hideContextMenu} - isContextMenuActive={this.state.reportAction.reportActionID === item.action.reportActionID} - showDeleteConfirmModal={this.showDeleteConfirmModal} /> ); } @@ -566,7 +410,7 @@ class ReportActionsView extends React.Component { } return ( - <> + - - - - - + + ); } } From 7d883ae0012aecf0cd16d0c15781a18be0253290 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Mon, 26 Jul 2021 02:05:15 +0530 Subject: [PATCH 16/84] cleanup --- .../report/ContextMenu/PopoverReportActionContextMenu.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js index 637b418727b61..60d50b16b4bdf 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js @@ -13,14 +13,11 @@ import ReportActionContextMenu from './ReportActionContextMenu'; import ConfirmModal from '../../../../components/ConfirmModal'; const propTypes = { - /** The report currently being looked at */ + /** Update the callbacks on ContextMenu Context */ setValue: PropTypes.func.isRequired, ...withLocalizePropTypes, }; -const defaultProps = { -}; - class PopoverReportActionContextMenu extends React.Component { constructor(props) { super(props); @@ -140,7 +137,6 @@ class PopoverReportActionContextMenu extends React.Component { } this.getContextMenuMeasuredLocation().then(({x, y}) => { if (!x || !y) { - console.debug('render skipped'); return; } this.setState(prev => ({ @@ -246,6 +242,6 @@ class PopoverReportActionContextMenu extends React.Component { } PopoverReportActionContextMenu.propTypes = propTypes; -PopoverReportActionContextMenu.defaultProps = defaultProps; +PopoverReportActionContextMenu.displayName = 'PopoverReportActionContextMenu'; export default withLocalize(PopoverReportActionContextMenu); From e0451f60f06a90873a525fdcc27bfde867b69a44 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Mon, 26 Jul 2021 17:42:12 +0530 Subject: [PATCH 17/84] refactor Context Menu --- .../report/ContextMenu/ContextMenuContext.js | 2 +- .../MiniReportActionContextMenu/index.js | 141 ++++-------------- .../PopoverReportActionContextMenu.js | 6 +- .../ContextMenu/ReportActionContextMenu.js | 76 +++++++--- ...s => ReportActionContextMenuPropsTypes.js} | 17 ++- src/pages/home/report/ReportActionItem.js | 29 ++-- 6 files changed, 113 insertions(+), 158 deletions(-) rename src/pages/home/report/ContextMenu/{MiniReportActionContextMenu/MiniReportActionContextMenuPropsTypes.js => ReportActionContextMenuPropsTypes.js} (61%) diff --git a/src/pages/home/report/ContextMenu/ContextMenuContext.js b/src/pages/home/report/ContextMenu/ContextMenuContext.js index 700fe6458a5b1..05df68498f071 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuContext.js +++ b/src/pages/home/report/ContextMenu/ContextMenuContext.js @@ -3,7 +3,7 @@ import React from 'react'; const ContextMenuContextDefaultValue = { showContextMenu: () => {}, hideContextMenu: () => {}, - isActionReportAction: () => {}, + isActiveReportAction: () => {}, showDeleteConfirmModal: () => {}, }; const ContextMenuContext = React.createContext(ContextMenuContextDefaultValue); diff --git a/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/index.js b/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/index.js index d31296f9f090b..89667415bcfcf 100644 --- a/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/index.js +++ b/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/index.js @@ -1,124 +1,33 @@ import _ from 'underscore'; import React from 'react'; import {View} from 'react-native'; -import lodashGet from 'lodash/get'; -import Str from 'expensify-common/lib/str'; +import PropTypes from 'prop-types'; import { - Clipboard as ClipboardIcon, LinkCopy, Mail, Pencil, Trashcan, Checkmark, -} from '../../../../../components/Icon/Expensicons'; -import getReportActionContextMenuStyles from '../../../../../styles/getReportActionContextMenuStyles'; -import { - setNewMarkerPosition, updateLastReadActionID, saveReportActionDraft, -} from '../../../../../libs/actions/Report'; -import ContextMenuItem from '../../../../../components/ContextMenuItem'; -import {propTypes, defaultProps} from './MiniReportActionContextMenuPropsTypes'; -import Clipboard from '../../../../../libs/Clipboard'; -import {isReportMessageAttachment, canEditReportAction, canDeleteReportAction} from '../../../../../libs/reportUtils'; + propTypes as ReportActionContextMenuPropsTypes, + defaultProps as ReportActionContextMenuDefaultProps, +} from '../ReportActionContextMenuPropsTypes'; import withLocalize from '../../../../../components/withLocalize'; - -class MiniReportActionContextMenu extends React.Component { - constructor(props) { - super(props); - - this.getActionText = this.getActionText.bind(this); - - // A list of all the context actions in this menu. - this.contextActions = [ - // Copy to clipboard - { - text: this.props.translate('contextMenuItem.copyToClipboard'), - icon: ClipboardIcon, - successText: this.props.translate('contextMenuItem.copied'), - successIcon: Checkmark, - shouldShow: true, - - // If return value is true, we switch the `text` and `icon` on - // `ContextMenuItem` with `successText` and `successIcon` which will fallback to - // the `text` and `icon` - onPress: () => { - const message = _.last(lodashGet(this.props.reportAction, 'message', null)); - const html = lodashGet(message, 'html', ''); - const text = Str.htmlDecode(props.selection || lodashGet(message, 'text', '')); - const isAttachment = _.has(this.props.reportAction, 'isAttachment') - ? this.props.reportAction.isAttachment - : isReportMessageAttachment(text); - if (!isAttachment) { - Clipboard.setString(text); - } else { - Clipboard.setString(html); - } - }, - }, - - { - text: this.props.translate('reportActionContextMenu.copyLink'), - icon: LinkCopy, - shouldShow: false, - onPress: () => {}, - }, - - { - text: this.props.translate('reportActionContextMenu.markAsUnread'), - icon: Mail, - successIcon: Checkmark, - shouldShow: true, - onPress: () => { - updateLastReadActionID(this.props.reportID, this.props.reportAction.sequenceNumber); - setNewMarkerPosition(this.props.reportID, this.props.reportAction.sequenceNumber); - }, - }, - - { - text: this.props.translate('reportActionContextMenu.editComment'), - icon: Pencil, - shouldShow: () => canEditReportAction(this.props.reportAction), - onPress: () => { - saveReportActionDraft( - this.props.reportID, - this.props.reportAction.reportActionID, - _.isEmpty(this.props.draftMessage) ? this.getActionText() : '', - ); - }, - }, - { - text: this.props.translate('reportActionContextMenu.deleteComment'), - icon: Trashcan, - shouldShow: () => canDeleteReportAction(this.props.reportAction), - onPress: () => { - this.props.showDeleteConfirmModal(this.props.reportID, this.props.reportAction); - }, - }, - ]; - } - - /** - * Gets the markdown version of the message in an action. - * - * @return {String} - */ - getActionText() { - const message = _.last(lodashGet(this.props.reportAction, 'message', null)); - return lodashGet(message, 'html', ''); - } - - render() { - return this.props.isVisible && ( - - {this.contextActions.map(contextAction => _.result(contextAction, 'shouldShow', false) && ( - contextAction.onPress(this.props.reportAction)} - /> - ))} - - ); - } -} +import {getMiniReportActionContextMenuWrapperStyle} from '../../../../../styles/getReportActionItemStyles'; +import ReportActionContextMenu from '../ReportActionContextMenu'; + +const propTypes = { + ..._.omit(ReportActionContextMenuPropsTypes, ['isMini']), + + /** Should the comment have the appearance of being grouped with the previous comment? */ + displayAsGroup: PropTypes.bool, +}; + +const defaultProps = { + ..._.omit(ReportActionContextMenuDefaultProps, ['isMini']), + displayAsGroup: false, +}; + +const MiniReportActionContextMenu = props => ( + + {/* eslint-disable-next-line react/jsx-props-no-spreading */} + + +); MiniReportActionContextMenu.propTypes = propTypes; MiniReportActionContextMenu.defaultProps = defaultProps; diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js index 60d50b16b4bdf..8f76881f550f8 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js @@ -51,14 +51,14 @@ class PopoverReportActionContextMenu extends React.Component { this.showDeleteConfirmModal = this.showDeleteConfirmModal.bind(this); this.contextMenuHidden = this.contextMenuHidden.bind(this); this.getContextMenuMeasuredLocation = this.getContextMenuMeasuredLocation.bind(this); - this.isActionReportAction = this.isActionReportAction.bind(this); + this.isActiveReportAction = this.isActiveReportAction.bind(this); } componentDidMount() { this.props.setValue({ showContextMenu: this.showContextMenu, hideContextMenu: this.hideContextMenu, - isActionReportAction: this.isActionReportAction, + isActiveReportAction: this.isActiveReportAction, showDeleteConfirmModal: this.showDeleteConfirmModal, }); Dimensions.addEventListener('change', this.measureContextMenuAnchorPosition); @@ -91,7 +91,7 @@ class PopoverReportActionContextMenu extends React.Component { }); } - isActionReportAction(actionId) { + isActiveReportAction(actionId) { return this.state.reportAction.reportActionID === actionId; } diff --git a/src/pages/home/report/ContextMenu/ReportActionContextMenu.js b/src/pages/home/report/ContextMenu/ReportActionContextMenu.js index 2f8015f3eb5e4..6569a8ae7a8b2 100755 --- a/src/pages/home/report/ContextMenu/ReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/ReportActionContextMenu.js @@ -13,22 +13,47 @@ import { } from '../../../../libs/actions/Report'; import ContextMenuItem from '../../../../components/ContextMenuItem'; import Clipboard from '../../../../libs/Clipboard'; -import { - propTypes as MiniReportActionContextMenuPropsTypes, - defaultProps as MiniReportActionContextMenuDefaultProps, -} from './MiniReportActionContextMenu/MiniReportActionContextMenuPropsTypes'; import {isReportMessageAttachment, canEditReportAction, canDeleteReportAction} from '../../../../libs/reportUtils'; -import withLocalize from '../../../../components/withLocalize'; +import withLocalize, {withLocalizePropTypes} from '../../../../components/withLocalize'; import ReportActionComposeFocusManager from '../../../../libs/ReportActionComposeFocusManager'; +import ReportActionPropTypes from '../ReportActionPropTypes'; const propTypes = { - ...MiniReportActionContextMenuPropsTypes, + /** The ID of the report this report action is attached to. */ + // eslint-disable-next-line react/no-unused-prop-types + reportID: PropTypes.number.isRequired, + + /** The report action this context menu is attached to. */ + reportAction: PropTypes.shape(ReportActionPropTypes).isRequired, + + /** If true, this component will be a small, row-oriented menu that displays icons but not text. + If false, this component will be a larger, column-oriented menu that displays icons alongside text in each row. */ + isMini: PropTypes.bool, + + /** Controls the visibility of this component. */ + isVisible: PropTypes.bool, + + /** The copy selection of text. */ + selection: PropTypes.string, + + /** Draft message - if this is set the comment is in 'edit' mode */ + draftMessage: PropTypes.string, + + /** Function to show the delete Action confirmation modal */ + showDeleteConfirmModal: PropTypes.func.isRequired, /** Function to dismiss the popover containing this menu */ hidePopover: PropTypes.func.isRequired, + + ...withLocalizePropTypes, }; -const defaultProps = MiniReportActionContextMenuDefaultProps; +const defaultProps = { + isMini: false, + isVisible: false, + selection: '', + draftMessage: '', +}; class ReportActionContextMenu extends React.Component { constructor(props) { @@ -62,7 +87,9 @@ class ReportActionContextMenu extends React.Component { } else { Clipboard.setString(html); } - this.hidePopover(true, ReportActionComposeFocusManager.focus); + if (!this.props.isMini) { + this.hidePopover(true, ReportActionComposeFocusManager.focus); + } }, }, @@ -81,7 +108,9 @@ class ReportActionContextMenu extends React.Component { onPress: () => { updateLastReadActionID(this.props.reportID, this.props.reportAction.sequenceNumber); setNewMarkerPosition(this.props.reportID, this.props.reportAction.sequenceNumber); - this.hidePopover(true, ReportActionComposeFocusManager.focus); + if (!this.props.isMini) { + this.hidePopover(true, ReportActionComposeFocusManager.focus); + } }, }, @@ -96,8 +125,13 @@ class ReportActionContextMenu extends React.Component { _.isEmpty(this.props.draftMessage) ? this.getActionText() : '', ); - // Hide popover, then call editAction - this.hidePopover(false, editAction); + if (this.props.isMini) { + // No popover to hide, call editAction immediately + editAction(); + } else { + // Hide popover, then call editAction + this.hidePopover(false, editAction); + } }, }, { @@ -105,14 +139,21 @@ class ReportActionContextMenu extends React.Component { icon: Trashcan, shouldShow: () => canDeleteReportAction(this.props.reportAction), onPress: () => { - // Hide popover, then call showDeleteConfirmModal - this.hidePopover( - false, - () => this.props.showDeleteConfirmModal(this.props.reportID, this.props.reportAction), - ); + if (this.props.isMini) { + // No popover to hide, call showDeleteConfirmModal immediately + this.props.showDeleteConfirmModal(this.props.reportID, this.props.reportAction); + } else { + // Hide popover, then call showDeleteConfirmModal + this.hidePopover( + false, + () => this.props.showDeleteConfirmModal(this.props.reportID, this.props.reportAction), + ); + } }, }, ]; + + this.wrapperStyle = getReportActionContextMenuStyles(this.props.isMini); } /** @@ -142,13 +183,14 @@ class ReportActionContextMenu extends React.Component { render() { return this.props.isVisible && ( - + {this.contextActions.map(contextAction => _.result(contextAction, 'shouldShow', false) && ( contextAction.onPress(this.props.reportAction)} /> diff --git a/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/MiniReportActionContextMenuPropsTypes.js b/src/pages/home/report/ContextMenu/ReportActionContextMenuPropsTypes.js similarity index 61% rename from src/pages/home/report/ContextMenu/MiniReportActionContextMenu/MiniReportActionContextMenuPropsTypes.js rename to src/pages/home/report/ContextMenu/ReportActionContextMenuPropsTypes.js index c603fe4a58f83..caf6c4f1c1b60 100644 --- a/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/MiniReportActionContextMenuPropsTypes.js +++ b/src/pages/home/report/ContextMenu/ReportActionContextMenuPropsTypes.js @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; -import {withLocalizePropTypes} from '../../../../../components/withLocalize'; -import ReportActionPropTypes from '../../ReportActionPropTypes'; +import {withLocalizePropTypes} from '../../../../components/withLocalize'; +import ReportActionPropTypes from '../ReportActionPropTypes'; const propTypes = { /** The ID of the report this report action is attached to. */ @@ -10,6 +10,10 @@ const propTypes = { /** The report action this context menu is attached to. */ reportAction: PropTypes.shape(ReportActionPropTypes).isRequired, + /** If true, this component will be a small, row-oriented menu that displays icons but not text. + If false, this component will be a larger, column-oriented menu that displays icons alongside text in each row. */ + isMini: PropTypes.bool, + /** Controls the visibility of this component. */ isVisible: PropTypes.bool, @@ -22,16 +26,17 @@ const propTypes = { /** Function to show the delete Action confirmation modal */ showDeleteConfirmModal: PropTypes.func.isRequired, + /** Function to dismiss the popover containing this menu */ + hidePopover: PropTypes.func.isRequired, + ...withLocalizePropTypes, }; const defaultProps = { + isMini: false, isVisible: false, selection: '', draftMessage: '', }; -export { - propTypes, - defaultProps, -}; +export {propTypes, defaultProps}; diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 5a7c8abe2368f..af9e354fa8a82 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -8,7 +8,6 @@ import ONYXKEYS from '../../../ONYXKEYS'; import ReportActionPropTypes from './ReportActionPropTypes'; import { getReportActionItemStyle, - getMiniReportActionContextMenuWrapperStyle, } from '../../../styles/getReportActionItemStyles'; import PressableWithSecondaryInteraction from '../../../components/PressableWithSecondaryInteraction'; import Hoverable from '../../../components/Hoverable'; @@ -135,7 +134,7 @@ class ReportActionItem extends Component { @@ -151,19 +150,19 @@ class ReportActionItem extends Component { )} - - - + )} From 00478fe9b39b8c518bcb571f12871a4b2cddfbcc Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Mon, 26 Jul 2021 19:30:35 +0530 Subject: [PATCH 18/84] refactor --- .../MiniReportActionContextMenu/index.js | 1 + .../PopoverReportActionContextMenu.js | 9 ++-- .../ContextMenu/ReportActionContextMenu.js | 42 +------------------ 3 files changed, 8 insertions(+), 44 deletions(-) diff --git a/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/index.js b/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/index.js index 89667415bcfcf..07f18814f3e8b 100644 --- a/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/index.js +++ b/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/index.js @@ -31,5 +31,6 @@ const MiniReportActionContextMenu = props => ( MiniReportActionContextMenu.propTypes = propTypes; MiniReportActionContextMenu.defaultProps = defaultProps; +MiniReportActionContextMenu.displayName = 'MiniReportActionContextMenu'; export default withLocalize(MiniReportActionContextMenu); diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js index 8f76881f550f8..0e838f4a23c65 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js @@ -79,7 +79,7 @@ class PopoverReportActionContextMenu extends React.Component { * We calculate the achor coordinates from measureInWindow async method * * @returns {Promise} - * @memberof ReportActionItem + * @memberof PopoverReportActionContextMenu */ getContextMenuMeasuredLocation() { return new Promise((res) => { @@ -104,7 +104,8 @@ class PopoverReportActionContextMenu extends React.Component { * @param {Number} reportID - Active Report Id * @param {Object} reportAction - ReportAction for ContextMenu * @param {String} draftMessage - ReportAction Draftmessage - * @memberof ReportActionsView + * @param {Function} [onShown=() => {}] + * @memberof PopoverReportActionContextMenu */ showContextMenu(event, selection, contextMenuAnchor, reportID, reportAction, draftMessage) { const nativeEvent = event.nativeEvent || {}; @@ -170,7 +171,7 @@ class PopoverReportActionContextMenu extends React.Component { * Used to calculate the Context Menu Dimensions * * @returns {JSX} - * @memberof ReportActionItem + * @memberof PopoverReportActionContextMenu */ measureContent() { return ( @@ -198,7 +199,7 @@ class PopoverReportActionContextMenu extends React.Component { * Opens the Confirm delete action modal * @param {Number} reportID * @param {Object} reportAction - * @memberof ReportActionsView + * @memberof PopoverReportActionContextMenu */ showDeleteConfirmModal(reportID, reportAction) { this.setState({reportID, reportAction, isDeleteCommentConfirmModalVisible: true}); diff --git a/src/pages/home/report/ContextMenu/ReportActionContextMenu.js b/src/pages/home/report/ContextMenu/ReportActionContextMenu.js index 0da234134b9f1..a80bfddbdfc3a 100755 --- a/src/pages/home/report/ContextMenu/ReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/ReportActionContextMenu.js @@ -1,7 +1,6 @@ import _ from 'underscore'; import React from 'react'; import {View} from 'react-native'; -import PropTypes from 'prop-types'; import lodashGet from 'lodash/get'; import Str from 'expensify-common/lib/str'; import { @@ -12,49 +11,12 @@ import { setNewMarkerPosition, updateLastReadActionID, saveReportActionDraft, } from '../../../../libs/actions/Report'; import ContextMenuItem from '../../../../components/ContextMenuItem'; -import ReportActionPropTypes from '../ReportActionPropTypes'; +import {propTypes, defaultProps} from './ReportActionContextMenuPropsTypes'; import Clipboard from '../../../../libs/Clipboard'; import {isReportMessageAttachment, canEditReportAction, canDeleteReportAction} from '../../../../libs/reportUtils'; -import withLocalize, {withLocalizePropTypes} from '../../../../components/withLocalize'; +import withLocalize from '../../../../components/withLocalize'; import ReportActionComposeFocusManager from '../../../../libs/ReportActionComposeFocusManager'; -const propTypes = { - /** The ID of the report this report action is attached to. */ - // eslint-disable-next-line react/no-unused-prop-types - reportID: PropTypes.number.isRequired, - - /** The report action this context menu is attached to. */ - reportAction: PropTypes.shape(ReportActionPropTypes).isRequired, - - /** If true, this component will be a small, row-oriented menu that displays icons but not text. - If false, this component will be a larger, column-oriented menu that displays icons alongside text in each row. */ - isMini: PropTypes.bool, - - /** Controls the visibility of this component. */ - isVisible: PropTypes.bool, - - /** The copy selection of text. */ - selection: PropTypes.string, - - /** Draft message - if this is set the comment is in 'edit' mode */ - draftMessage: PropTypes.string, - - /** Function to dismiss the popover containing this menu */ - hidePopover: PropTypes.func.isRequired, - - /** Function to show the delete Action confirmation modal */ - showDeleteConfirmModal: PropTypes.func.isRequired, - - ...withLocalizePropTypes, -}; - -const defaultProps = { - isMini: false, - isVisible: false, - selection: '', - draftMessage: '', -}; - class ReportActionContextMenu extends React.Component { constructor(props) { super(props); From b1b987737810f297f4125513b57918e3c4460ed5 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Mon, 26 Jul 2021 19:46:52 +0530 Subject: [PATCH 19/84] fix: realtime update issue with menus --- .../PopoverReportActionContextMenu.js | 24 +++++++++++++------ .../ContextMenu/ReportActionContextMenu.js | 6 ++++- src/pages/home/report/ReportActionItem.js | 5 ++++ 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js index 0e838f4a23c65..39fca06fbec48 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js @@ -25,8 +25,9 @@ class PopoverReportActionContextMenu extends React.Component { this.state = { reportID: 0, reportAction: {}, - isPopoverVisible: false, + selection: '', reportActionDraftMessage: '', + isPopoverVisible: false, isDeleteCommentConfirmModalVisible: false, cursorRelativePosition: { horizontal: 0, @@ -38,7 +39,6 @@ class PopoverReportActionContextMenu extends React.Component { horizontal: 0, vertical: 0, }, - selection: '', }; this.onPopoverHide = () => {}; this.contextMenuAchor = undefined; @@ -104,10 +104,10 @@ class PopoverReportActionContextMenu extends React.Component { * @param {Number} reportID - Active Report Id * @param {Object} reportAction - ReportAction for ContextMenu * @param {String} draftMessage - ReportAction Draftmessage - * @param {Function} [onShown=() => {}] + * @param {Function} [onShown=() => {}] - Run a callback when Menu is shown * @memberof PopoverReportActionContextMenu */ - showContextMenu(event, selection, contextMenuAnchor, reportID, reportAction, draftMessage) { + showContextMenu(event, selection, contextMenuAnchor, reportID, reportAction, draftMessage, onShown = () => {}) { const nativeEvent = event.nativeEvent || {}; this.contextMenuAchor = contextMenuAnchor; this.getContextMenuMeasuredLocation().then(({x, y}) => { @@ -125,7 +125,7 @@ class PopoverReportActionContextMenu extends React.Component { selection, isPopoverVisible: true, reportActionDraftMessage: draftMessage, - }); + }, onShown); }); } @@ -164,7 +164,13 @@ class PopoverReportActionContextMenu extends React.Component { if (_.isFunction(onHideCallback)) { this.onPopoverHide = onHideCallback; } - this.setState({isPopoverVisible: false}); + this.setState({ + reportID: 0, + reportAction: {}, + selection: '', + reportActionDraftMessage: '', + isPopoverVisible: false, + }); } /** @@ -192,7 +198,11 @@ class PopoverReportActionContextMenu extends React.Component { } hideDeleteConfirmModal() { - this.setState({isDeleteCommentConfirmModalVisible: false}); + this.setState({ + reportID: 0, + reportAction: {}, + isDeleteCommentConfirmModalVisible: false, + }); } /** diff --git a/src/pages/home/report/ContextMenu/ReportActionContextMenu.js b/src/pages/home/report/ContextMenu/ReportActionContextMenu.js index a80bfddbdfc3a..8c98b26fcdf29 100755 --- a/src/pages/home/report/ContextMenu/ReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/ReportActionContextMenu.js @@ -105,10 +105,14 @@ class ReportActionContextMenu extends React.Component { // No popover to hide, call showDeleteConfirmModal immediately this.props.showDeleteConfirmModal(this.props.reportID, this.props.reportAction); } else { + // preserve the copy of props so that they don't reset on prop changes during hidePopover call + const reportID = this.props.reportID; + const reportAction = this.props.reportAction; + // Hide popover, then call showDeleteConfirmModal this.hidePopover( false, - () => this.props.showDeleteConfirmModal(this.props.reportID, this.props.reportAction), + () => this.props.showDeleteConfirmModal(reportID, reportAction), ); } }, diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index af9e354fa8a82..25279dc633dc8 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -93,6 +93,10 @@ class ReportActionItem extends Component { this.props.reportID, this.props.action, this.props.draftMessage, + () => { + // ForceUpdate to hide the Mini menu when PopoverMenu is shown + this.forceUpdate(); + }, ); } @@ -158,6 +162,7 @@ class ReportActionItem extends Component { hovered && !this.context.isActiveReportAction(this.props.action.reportActionID) && !this.props.draftMessage + } draftMessage={this.props.draftMessage} hidePopover={this.context.hideContextMenu} From bbc245dedc5924e6bdc0bd30a7da4c30f0af0bb5 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Mon, 26 Jul 2021 17:17:49 -0600 Subject: [PATCH 20/84] Don't get paypal address in user details --- src/libs/actions/User.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/User.js b/src/libs/actions/User.js index 6e7c895b6552a..e2428a124d04a 100644 --- a/src/libs/actions/User.js +++ b/src/libs/actions/User.js @@ -69,7 +69,7 @@ function getBetas() { function getUserDetails() { API.Get({ returnValueList: 'account, loginList, nameValuePairs', - nvpNames: [CONST.NVP.BLOCKED_FROM_CONCIERGE, CONST.NVP.PAYPAL_ME_ADDRESS].join(','), + nvpNames: CONST.NVP.PAYPAL_ME_ADDRESS, }) .then((response) => { // Update the User onyx key From d77e5d84b2fcf51b24c1692f87c602a2319defee Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Mon, 26 Jul 2021 17:53:18 -0600 Subject: [PATCH 21/84] Don't hide add payment button --- .../settings/Payments/PaymentMethodList.js | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/pages/settings/Payments/PaymentMethodList.js b/src/pages/settings/Payments/PaymentMethodList.js index c76ac9024f4e4..4b1de753a7fd0 100644 --- a/src/pages/settings/Payments/PaymentMethodList.js +++ b/src/pages/settings/Payments/PaymentMethodList.js @@ -130,19 +130,14 @@ class PaymentMethodList extends Component { }); } - // Don't show Add Payment Method button if user provided details for all possible payment methods. - // Right now only available method is Paypal.me - // When there is a new payment method, it needs to be added to following if condition. - if (!this.props.payPalMeUsername) { - combinedPaymentMethods.push({ - type: MENU_ITEM, - title: this.props.translate('paymentMethodList.addPaymentMethod'), - icon: Plus, - onPress: e => this.props.onPress(e), - key: 'addPaymentMethodButton', - disabled: this.props.isLoadingPayments, - }); - } + combinedPaymentMethods.push({ + type: MENU_ITEM, + title: this.props.translate('paymentMethodList.addPaymentMethod'), + icon: Plus, + onPress: e => this.props.onPress(e), + key: 'addPaymentMethodButton', + disabled: this.props.isLoadingPayments, + }); return combinedPaymentMethods; } From fa67efba11b5d0080c9714d9c852cb15516b27b9 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Mon, 26 Jul 2021 17:56:55 -0600 Subject: [PATCH 22/84] Return to payments page after setting paypal username --- src/pages/settings/Payments/AddPayPalMePage.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pages/settings/Payments/AddPayPalMePage.js b/src/pages/settings/Payments/AddPayPalMePage.js index 46928e99cd7b0..8034b3801fa2f 100644 --- a/src/pages/settings/Payments/AddPayPalMePage.js +++ b/src/pages/settings/Payments/AddPayPalMePage.js @@ -9,7 +9,7 @@ import HeaderWithCloseButton from '../../../components/HeaderWithCloseButton'; import Text from '../../../components/Text'; import ScreenWrapper from '../../../components/ScreenWrapper'; import NameValuePair from '../../../libs/actions/NameValuePair'; -import {getUserDetails} from '../../../libs/actions/User'; +import getPaymentMethods from '../../../libs/actions/PaymentMethods'; import Navigation from '../../../libs/Navigation/Navigation'; import styles from '../../../styles/styles'; import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; @@ -42,7 +42,7 @@ class AddPayPalMePage extends React.Component { } componentDidMount() { - getUserDetails(); + getPaymentMethods(); } componentDidUpdate(prevProps) { @@ -59,6 +59,7 @@ class AddPayPalMePage extends React.Component { setPayPalMeUsername() { NameValuePair.set(CONST.NVP.PAYPAL_ME_ADDRESS, this.state.payPalMeUsername, ONYXKEYS.NVP_PAYPAL_ME_ADDRESS); Growl.show(this.props.translate('addPayPalMePage.growlMessageOnSave'), CONST.GROWL.SUCCESS, 3000); + Navigation.navigate(ROUTES.SETTINGS_PAYMENTS); } render() { From be75beb9507caca162db897ff933e62b45ed1e34 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Mon, 26 Jul 2021 19:36:22 -0600 Subject: [PATCH 23/84] Always show add paypal.me button --- src/pages/settings/Payments/PaymentsPage.js | 23 +++++---------------- 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/src/pages/settings/Payments/PaymentsPage.js b/src/pages/settings/Payments/PaymentsPage.js index 3d266d1cfa489..70296695c1112 100644 --- a/src/pages/settings/Payments/PaymentsPage.js +++ b/src/pages/settings/Payments/PaymentsPage.js @@ -1,8 +1,5 @@ import React from 'react'; import {View} from 'react-native'; -import PropTypes from 'prop-types'; -import {withOnyx} from 'react-native-onyx'; -import ONYXKEYS from '../../../ONYXKEYS'; import PaymentMethodList from './PaymentMethodList'; import ROUTES from '../../../ROUTES'; import HeaderWithCloseButton from '../../../components/HeaderWithCloseButton'; @@ -23,9 +20,6 @@ import CurrentWalletBalance from '../../../components/CurrentWalletBalance'; const PAYPAL = 'payPalMe'; const propTypes = { - /** User's paypal.me username if they have one */ - payPalMeUsername: PropTypes.string, - ...withLocalizePropTypes, }; @@ -129,13 +123,11 @@ class PaymentsPage extends React.Component { left: this.state.anchorPositionLeft, }} > - {!this.props.payPalMeUsername && ( - this.addPaymentMethodTypePressed(PAYPAL)} - /> - )} + this.addPaymentMethodTypePressed(PAYPAL)} + /> @@ -149,9 +141,4 @@ PaymentsPage.displayName = 'PaymentsPage'; export default compose( withLocalize, - withOnyx({ - payPalMeUsername: { - key: ONYXKEYS.NVP_PAYPAL_ME_ADDRESS, - }, - }), )(PaymentsPage); From d1286e98cb5927274e901ecc288fb4a92d7d61c9 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Tue, 27 Jul 2021 11:33:52 -0600 Subject: [PATCH 24/84] Change button to make more sense when editing paypal.me username --- src/languages/en.js | 1 + src/languages/es.js | 3 ++- src/pages/settings/Payments/AddPayPalMePage.js | 4 +++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/languages/en.js b/src/languages/en.js index 0d38c1bcf1025..b21ec2fe50942 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -240,6 +240,7 @@ export default { payPalMe: 'PayPal.me/', yourPayPalUsername: 'Your PayPal username', addPayPalAccount: 'Add PayPal Account', + editPayPalAccount: 'Update PayPal Account', growlMessageOnSave: 'Your PayPal username was successfully added', }, paymentsPage: { diff --git a/src/languages/es.js b/src/languages/es.js index 6742589880661..f64ab3df8e510 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -231,7 +231,8 @@ export default { enterYourUsernameToGetPaidViaPayPal: 'Escribe tu nombre de usuario para que otros puedan pagarte a través de PayPal.', payPalMe: 'PayPal.me/', yourPayPalUsername: 'Tu usuario de PayPal', - addPayPalAccount: 'Agregar Cuenta de Paypal', + addPayPalAccount: 'Agregar Cuenta de PayPal', + editPayPalAccount: 'Actualizar Cuenta de PayPal', }, paymentsPage: { paymentMethodsTitle: 'Métodos de pago', diff --git a/src/pages/settings/Payments/AddPayPalMePage.js b/src/pages/settings/Payments/AddPayPalMePage.js index 8034b3801fa2f..97edbe4237731 100644 --- a/src/pages/settings/Payments/AddPayPalMePage.js +++ b/src/pages/settings/Payments/AddPayPalMePage.js @@ -103,7 +103,9 @@ class AddPayPalMePage extends React.Component { onPress={this.setPayPalMeUsername} pressOnEnter style={[styles.mt3]} - text={this.props.translate('addPayPalMePage.addPayPalAccount')} + text={this.props.payPalMeUsername + ? this.props.translate('addPayPalMePage.editPayPalAccount') + : this.props.translate('addPayPalMePage.addPayPalAccount')} /> From 4acf891537efc83231e494bcd32b98754e802959 Mon Sep 17 00:00:00 2001 From: Jasper Huang Date: Tue, 27 Jul 2021 15:36:23 -0700 Subject: [PATCH 25/84] Fix bolded areas and cleanup --- src/libs/actions/BankAccounts.js | 14 +++-------- .../ReimbursementAccount/BankAccountStep.js | 25 ++++++++++++++----- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/libs/actions/BankAccounts.js b/src/libs/actions/BankAccounts.js index 53434116618ba..168bac43e8877 100644 --- a/src/libs/actions/BankAccounts.js +++ b/src/libs/actions/BankAccounts.js @@ -618,18 +618,12 @@ function setupWithdrawalAccount(data) { // Show warning if another account already set up this bank account and promote share if (response.existingOwners) { console.error('Cannot set up withdrawal account due to existing owners', response); - const existingOwnersList = response.existingOwners.reduce((ownersStr, owner, i, ownersArr) => { - let separator = ',\n'; - if (i === 0) { - separator = '\n'; - } else if (i === ownersArr.length - 1) { - separator = ' and\n'; - } - return `${ownersStr}${separator}${owner}`; - }, ''); Onyx.merge( ONYXKEYS.REIMBURSEMENT_ACCOUNT, - {existingOwnersList, error: CONST.BANK_ACCOUNT.ERROR.EXISTING_OWNERS}, + { + existingOwners: response.existingOwners, + error: CONST.BANK_ACCOUNT.ERROR.EXISTING_OWNERS, + }, ); return; } diff --git a/src/pages/ReimbursementAccount/BankAccountStep.js b/src/pages/ReimbursementAccount/BankAccountStep.js index db8cce8eaee63..33aeb1edb4b61 100644 --- a/src/pages/ReimbursementAccount/BankAccountStep.js +++ b/src/pages/ReimbursementAccount/BankAccountStep.js @@ -36,8 +36,8 @@ const propTypes = { /** Error set when handling the API response */ error: PropTypes.string, - /** A list of existing owners, set if the bank account being added is already owned */ - existingOwnersList: PropTypes.string, + /** The existing owners for if the bank account is already owned */ + existingOwners: PropTypes.arrayOf(PropTypes.string), }).isRequired, ...withLocalizePropTypes, @@ -129,8 +129,9 @@ class BankAccountStep extends React.Component { // Disable bank account fields once they've been added in db so they can't be changed const isFromPlaid = this.props.achData.setupType === CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID; const shouldDisableInputs = Boolean(this.props.achData.bankAccountID) || isFromPlaid; + const existingOwners = this.props.reimbursementAccount.existingOwners; const isExistingOwnersErrorVisible = Boolean(this.props.reimbursementAccount.error - && this.props.reimbursementAccount.existingOwnersList); + && existingOwners); return ( {this.props.translate('bankAccount.error.existingOwners.alreadyInUse')} - - {this.props.reimbursementAccount.existingOwnersList} - + {existingOwners && existingOwners.map((existingOwner, i) => { + let separator = ', '; + if (i === 0) { + separator = ''; + } else if (i === existingOwners.length - 1) { + separator = ` ${this.props.translate('common.and')} `; + } + return ( + <> + {separator} + {existingOwner} + {i === existingOwners.length - 1 && .} + + ); + })} {this.props.translate('bankAccount.error.existingOwners.pleaseAskThemToShare')} From 7a57dc9b01d3569b5417f022f9ff469858513a18 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 28 Jul 2021 03:30:49 +0300 Subject: [PATCH 26/84] add isValidSize func --- src/components/AttachmentModal.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index dfd81620d98ab..3c581be4ca69a 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -63,6 +63,7 @@ class AttachmentModal extends PureComponent { }; this.submitAndClose = this.submitAndClose.bind(this); + this.isValidSize = this.isValidSize.bind(this); } /** @@ -81,6 +82,18 @@ class AttachmentModal extends PureComponent { this.setState({isModalOpen: false}); } + /** + * Check if the attachment file is less than the API size limit. + * @param {Object} file + * @returns {Boolean} + */ + isValidSize(file) { + if (!file) { + return true; + } + return parseInt(file.size, 10) < CONST.API_MAX_ATTACHMENT_SIZE; + } + render() { const sourceURL = this.props.isAuthTokenRequired ? addEncryptedAuthTokenToURL(this.state.sourceURL) From c1d0184076bf90d3a47b901f34392e3787be3f75 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 28 Jul 2021 03:36:41 +0300 Subject: [PATCH 27/84] add language --- src/languages/en.js | 1 + src/languages/es.js | 1 + 2 files changed, 2 insertions(+) diff --git a/src/languages/en.js b/src/languages/en.js index 0d38c1bcf1025..55bb76a1b41f7 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -70,6 +70,7 @@ export default { takePhoto: 'Take Photo', chooseFromGallery: 'Choose from Gallery', chooseDocument: 'Choose Document', + sizeExceeded: 'Attachment size is larger than 50 MB limit', }, textInputFocusable: { noExtentionFoundForMimeType: 'No extension found for mime type', diff --git a/src/languages/es.js b/src/languages/es.js index 6742589880661..58c604363e5f2 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -65,6 +65,7 @@ export default { takePhoto: 'Hacer una Foto', chooseFromGallery: 'Elegir de la galería', chooseDocument: 'Elegir Documento', + sizeExceeded: 'El archivo adjunto supera el límite de 50 MB', }, textInputFocusable: { noExtentionFoundForMimeType: 'No se encontró una extension para este tipo de contenido', From bcca87884496575876caa6b33f82aa3e6e2671cc Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 28 Jul 2021 03:45:09 +0300 Subject: [PATCH 28/84] add api size limit to CONST --- src/CONST.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/CONST.js b/src/CONST.js index ac5bda74851cc..9b5d985793424 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -1,6 +1,8 @@ const CLOUDFRONT_URL = 'https://d2k5nsl2zxldvw.cloudfront.net'; const CONST = { + // 50 MB in bytes + API_MAX_ATTACHMENT_SIZE: 52428800, APP_DOWNLOAD_LINKS: { ANDROID: 'https://play.google.com/store/apps/details?id=com.expensify.chat', IOS: 'https://apps.apple.com/us/app/expensify-cash/id1530278510', From 68cc9ce48dc69c7bb5fe498140b7e0524fb3ceb4 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Wed, 28 Jul 2021 11:23:41 +0530 Subject: [PATCH 29/84] small changes --- src/components/Badge.js | 4 ++-- src/components/MenuItem.js | 2 +- src/pages/settings/InitialPage.js | 4 +++- src/styles/styles.js | 4 ++-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/components/Badge.js b/src/components/Badge.js index 26ee47648c694..84399afce0b92 100644 --- a/src/components/Badge.js +++ b/src/components/Badge.js @@ -5,10 +5,10 @@ import styles, {getBadgeColorStyle} from '../styles/styles'; import Text from './Text'; const propTypes = { - /** Is success type */ + /** Is Success type */ success: PropTypes.bool, - /** Is success type */ + /** Is Error type */ error: PropTypes.bool, /** Whether badge is clickable */ diff --git a/src/components/MenuItem.js b/src/components/MenuItem.js index 36bc13ba18066..f046068643e1a 100644 --- a/src/components/MenuItem.js +++ b/src/components/MenuItem.js @@ -11,7 +11,7 @@ import getButtonState from '../libs/getButtonState'; import Badge from './Badge'; const propTypes = { - // Text to be shown as badge near the right end. + /** Text to be shown as badge near the right end. */ badgeText: PropTypes.string, /** Any additional styles to apply */ diff --git a/src/pages/settings/InitialPage.js b/src/pages/settings/InitialPage.js index 880016fc2b37b..064691876484a 100755 --- a/src/pages/settings/InitialPage.js +++ b/src/pages/settings/InitialPage.js @@ -81,7 +81,9 @@ const defaultProps = { network: {}, session: {}, policies: {}, - userWallet: {}, + userWallet: { + availableBalance: 0, + }, }; const defaultMenuItems = [ diff --git a/src/styles/styles.js b/src/styles/styles.js index 9aeb38318f68d..6d5258bd0bf51 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -2014,8 +2014,8 @@ function getBackgroundColorStyle(backgroundColor) { /** * Generate a style for the background color of the Badge * - * @param {*} success - * @param {*} error + * @param {Boolean} success + * @param {Boolean} error * @param {boolean} [isPressed=false] * @return {Object} */ From 0277089d973bd1eec5ddfa51fd153e0e203c6cb3 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 28 Jul 2021 12:40:59 +0300 Subject: [PATCH 30/84] show confirm modal for large attachment --- src/CONST.js | 2 +- src/components/AttachmentModal.js | 26 ++++++++++++++++++++++++-- src/languages/en.js | 1 + src/languages/es.js | 1 + 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/CONST.js b/src/CONST.js index 9b5d985793424..0a2ec92fd476e 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -1,7 +1,7 @@ const CLOUDFRONT_URL = 'https://d2k5nsl2zxldvw.cloudfront.net'; const CONST = { - // 50 MB in bytes + // 50 megabytes in bytes API_MAX_ATTACHMENT_SIZE: 52428800, APP_DOWNLOAD_LINKS: { ANDROID: 'https://play.google.com/store/apps/details?id=com.expensify.chat', diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index 3c581be4ca69a..362da6f393a67 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -14,6 +14,7 @@ import Button from './Button'; import HeaderWithCloseButton from './HeaderWithCloseButton'; import fileDownload from '../libs/fileDownload'; import withLocalize, {withLocalizePropTypes} from './withLocalize'; +import ConfirmModal from './ConfirmModal'; /** * Modal render prop component that exposes modal launching triggers that can be used @@ -58,11 +59,13 @@ class AttachmentModal extends PureComponent { this.state = { isModalOpen: false, + isConfirmModalOpen: false, file: null, sourceURL: props.sourceURL, }; this.submitAndClose = this.submitAndClose.bind(this); + this.closeConfirmModal = this.closeConfirmModal.bind(this); this.isValidSize = this.isValidSize.bind(this); } @@ -83,8 +86,15 @@ class AttachmentModal extends PureComponent { } /** - * Check if the attachment file is less than the API size limit. - * @param {Object} file + * Close the confirm modal. + */ + closeConfirmModal() { + this.setState({isConfirmModalOpen: false}); + } + + /** + * Check if the attachment size is less than the API size limit. + * @param {Object} file * @returns {Boolean} */ isValidSize(file) { @@ -147,8 +157,20 @@ class AttachmentModal extends PureComponent { /> )} + {this.props.children({ displayFileInModal: ({file}) => { + if (!this.isValidSize(file)) { + this.setState({isConfirmModalOpen: true}); + return; + } if (file instanceof File) { const source = URL.createObjectURL(file); this.setState({isModalOpen: true, sourceURL: source, file}); diff --git a/src/languages/en.js b/src/languages/en.js index 55bb76a1b41f7..a16534c2488f9 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -70,6 +70,7 @@ export default { takePhoto: 'Take Photo', chooseFromGallery: 'Choose from Gallery', chooseDocument: 'Choose Document', + attachmentTooLarge: 'Attachment too large', sizeExceeded: 'Attachment size is larger than 50 MB limit', }, textInputFocusable: { diff --git a/src/languages/es.js b/src/languages/es.js index 58c604363e5f2..81bccd2729a07 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -65,6 +65,7 @@ export default { takePhoto: 'Hacer una Foto', chooseFromGallery: 'Elegir de la galería', chooseDocument: 'Elegir Documento', + attachmentTooLarge: 'Archivo adjunto demasiado grandes', sizeExceeded: 'El archivo adjunto supera el límite de 50 MB', }, textInputFocusable: { From a409bdb2b397f1b8a017ab6ca2ec892604381e39 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Wed, 28 Jul 2021 15:30:45 +0530 Subject: [PATCH 31/84] fix: outline across platforms --- src/styles/addOutlineWidth/index.js | 3 +++ web/index.html | 9 +++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/styles/addOutlineWidth/index.js b/src/styles/addOutlineWidth/index.js index 96868ba9d9420..566a1ef34df4d 100644 --- a/src/styles/addOutlineWidth/index.js +++ b/src/styles/addOutlineWidth/index.js @@ -3,6 +3,8 @@ * can be added to the object */ +import themeDefault from '../themes/default'; + /** * Adds the addOutlineWidth property to an object to be used when styling * @@ -14,6 +16,7 @@ function withOutlineWidth(obj, val) { return { ...obj, outlineWidth: val, + boxShadow: `0px 0px 0px ${val}px ${themeDefault.borderFocus}`, }; } diff --git a/web/index.html b/web/index.html index 1c55f9bffbc4e..a96e576510c2b 100644 --- a/web/index.html +++ b/web/index.html @@ -32,8 +32,13 @@ -webkit-user-select: none !important; -webkit-touch-callout: none !important; } - *:focus, *:focus-visible, [data-focusvisible-polyfill] { - outline: 1px solid #0185ff; + :focus-visible { + outline: 0; + box-shadow: 0px 0px 0px 1px #0185ff; + } + :focus[data-focusvisible-polyfill] { + outline: 0; + box-shadow: 0px 0px 0px 1px #0185ff; } From 5864478f4cf53af0365ac290ba35ec2d1bc05d12 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 28 Jul 2021 13:19:29 +0300 Subject: [PATCH 32/84] fix punctuation --- src/languages/en.js | 2 +- src/languages/es.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/languages/en.js b/src/languages/en.js index a16534c2488f9..e920a8422f63b 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -71,7 +71,7 @@ export default { chooseFromGallery: 'Choose from Gallery', chooseDocument: 'Choose Document', attachmentTooLarge: 'Attachment too large', - sizeExceeded: 'Attachment size is larger than 50 MB limit', + sizeExceeded: 'Attachment size is larger than 50 MB limit.', }, textInputFocusable: { noExtentionFoundForMimeType: 'No extension found for mime type', diff --git a/src/languages/es.js b/src/languages/es.js index 81bccd2729a07..82a3910a28219 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -66,7 +66,7 @@ export default { chooseFromGallery: 'Elegir de la galería', chooseDocument: 'Elegir Documento', attachmentTooLarge: 'Archivo adjunto demasiado grandes', - sizeExceeded: 'El archivo adjunto supera el límite de 50 MB', + sizeExceeded: 'El archivo adjunto supera el límite de 50 MB.', }, textInputFocusable: { noExtentionFoundForMimeType: 'No se encontró una extension para este tipo de contenido', From 9e4d97891a3130534073d05b241679e3bbf30ffe Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 28 Jul 2021 14:58:05 +0300 Subject: [PATCH 33/84] fix Spanish translation --- src/languages/es.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/es.js b/src/languages/es.js index 82a3910a28219..6a7e78d89d99d 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -65,7 +65,7 @@ export default { takePhoto: 'Hacer una Foto', chooseFromGallery: 'Elegir de la galería', chooseDocument: 'Elegir Documento', - attachmentTooLarge: 'Archivo adjunto demasiado grandes', + attachmentTooLarge: 'Archivo adjunto demasiado grande', sizeExceeded: 'El archivo adjunto supera el límite de 50 MB.', }, textInputFocusable: { From df3c6e94787d796818def86a8b8d211a77a7ee38 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Wed, 28 Jul 2021 18:28:15 +0530 Subject: [PATCH 34/84] refactor Context Menu --- .../BaseReportActionContextMenu.js | 45 ++++ .../report/ContextMenu/ContextMenuActions.js | 116 +++++++++ .../report/ContextMenu/ContextMenuContext.js | 12 - .../MiniReportActionContextMenu/index.js | 7 +- .../PopoverReportActionContextMenu.js | 15 +- .../ContextMenu/ReportActionContextMenu.js | 227 ++++++------------ .../ReportActionContextMenuPropsTypes.js | 6 - src/pages/home/report/ReportActionItem.js | 18 +- src/pages/home/report/ReportActionsView.js | 11 +- 9 files changed, 249 insertions(+), 208 deletions(-) create mode 100755 src/pages/home/report/ContextMenu/BaseReportActionContextMenu.js create mode 100644 src/pages/home/report/ContextMenu/ContextMenuActions.js delete mode 100644 src/pages/home/report/ContextMenu/ContextMenuContext.js mode change 100755 => 100644 src/pages/home/report/ContextMenu/ReportActionContextMenu.js diff --git a/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.js b/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.js new file mode 100755 index 0000000000000..7fd9aabbfcd6d --- /dev/null +++ b/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.js @@ -0,0 +1,45 @@ +import React from 'react'; +import {View} from 'react-native'; +import getReportActionContextMenuStyles from '../../../../styles/getReportActionContextMenuStyles'; +import ContextMenuItem from '../../../../components/ContextMenuItem'; +import {propTypes, defaultProps} from './ReportActionContextMenuPropsTypes'; +import withLocalize from '../../../../components/withLocalize'; +import ContextMenuActions from './ContextMenuActions'; + +class BaseReportActionContextMenu extends React.Component { + constructor(props) { + super(props); + + this.wrapperStyle = getReportActionContextMenuStyles(this.props.isMini); + } + + render() { + return this.props.isVisible && ( + + {ContextMenuActions.map(contextAction => contextAction.shouldShow(this.props.reportAction) && ( + contextAction.onPress(!this.props.isMini, { + reportAction: this.props.reportAction, + reportID: this.props.reportID, + draftMessage: this.props.draftMessage, + selection: this.props.selection, + })} + /> + ))} + + ); + } +} + +BaseReportActionContextMenu.propTypes = propTypes; +BaseReportActionContextMenu.defaultProps = defaultProps; + +export default withLocalize(BaseReportActionContextMenu); diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js new file mode 100644 index 0000000000000..1b0c1f2dd57bc --- /dev/null +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js @@ -0,0 +1,116 @@ +import _ from 'underscore'; +import lodashGet from 'lodash/get'; +import Str from 'expensify-common/lib/str'; +import { + Clipboard as ClipboardIcon, LinkCopy, Mail, Pencil, Trashcan, Checkmark, +} from '../../../../components/Icon/Expensicons'; +import { + setNewMarkerPosition, updateLastReadActionID, saveReportActionDraft, +} from '../../../../libs/actions/Report'; +import Clipboard from '../../../../libs/Clipboard'; +import {isReportMessageAttachment, canEditReportAction, canDeleteReportAction} from '../../../../libs/reportUtils'; +import ReportActionComposeFocusManager from '../../../../libs/ReportActionComposeFocusManager'; +import {hideContextMenu, showDeleteConfirmModal} from './ReportActionContextMenu'; + +/** + * Gets the markdown version of the message in an action. + * @param {Object} reportAction + * @return {String} + */ +function getActionText(reportAction) { + const message = _.last(lodashGet(reportAction, 'message', null)); + return lodashGet(message, 'html', ''); +} + +// A list of all the context actions in this menu. +export default [ + // Copy to clipboard + { + textTranslateKey: 'contextMenuItem.copyToClipboard', + icon: ClipboardIcon, + successTextTranslateKey: 'contextMenuItem.copied', + successIcon: Checkmark, + shouldShow: () => true, + + // If return value is true, we switch the `text` and `icon` on + // `ContextMenuItem` with `successText` and `successIcon` which will fallback to + // the `text` and `icon` + onPress: (closePopover, {reportAction, selection}) => { + const message = _.last(lodashGet(reportAction, 'message', null)); + const html = lodashGet(message, 'html', ''); + const text = Str.htmlDecode(selection || lodashGet(message, 'text', '')); + const isAttachment = _.has(reportAction, 'isAttachment') + ? reportAction.isAttachment + : isReportMessageAttachment(text); + if (!isAttachment) { + Clipboard.setString(text); + } else { + Clipboard.setString(html); + } + if (closePopover) { + hideContextMenu(true, ReportActionComposeFocusManager.focus); + } + }, + }, + + { + textTranslateKey: 'reportActionContextMenu.copyLink', + icon: LinkCopy, + shouldShow: () => false, + onPress: () => {}, + }, + + { + textTranslateKey: 'reportActionContextMenu.markAsUnread', + icon: Mail, + successIcon: Checkmark, + shouldShow: () => true, + onPress: (closePopover, {reportAction, reportID}) => { + updateLastReadActionID(reportID, reportAction.sequenceNumber); + setNewMarkerPosition(reportID, reportAction.sequenceNumber); + if (closePopover) { + hideContextMenu(true, ReportActionComposeFocusManager.focus); + } + }, + }, + + { + textTranslateKey: 'reportActionContextMenu.editComment', + icon: Pencil, + shouldShow: reportAction => canEditReportAction(reportAction), + onPress: (closePopover, {reportID, reportAction, draftMessage}) => { + const editAction = () => saveReportActionDraft( + reportID, + reportAction.reportActionID, + _.isEmpty(draftMessage) ? getActionText(reportAction) : '', + ); + + if (closePopover) { + // Hide popover, then call editAction + hideContextMenu(false, editAction); + return; + } + + // No popover to hide, call editAction immediately + editAction(); + }, + }, + { + textTranslateKey: 'reportActionContextMenu.deleteComment', + icon: Trashcan, + shouldShow: reportAction => canDeleteReportAction(reportAction), + onPress: (closePopover, {reportID, reportAction}) => { + if (closePopover) { + // Hide popover, then call showDeleteConfirmModal + hideContextMenu( + false, + () => showDeleteConfirmModal(reportID, reportAction), + ); + return; + } + + // No popover to hide, call showDeleteConfirmModal immediately + showDeleteConfirmModal(reportID, reportAction); + }, + }, +]; diff --git a/src/pages/home/report/ContextMenu/ContextMenuContext.js b/src/pages/home/report/ContextMenu/ContextMenuContext.js deleted file mode 100644 index 05df68498f071..0000000000000 --- a/src/pages/home/report/ContextMenu/ContextMenuContext.js +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; - -const ContextMenuContextDefaultValue = { - showContextMenu: () => {}, - hideContextMenu: () => {}, - isActiveReportAction: () => {}, - showDeleteConfirmModal: () => {}, -}; -const ContextMenuContext = React.createContext(ContextMenuContextDefaultValue); -ContextMenuContext.displayName = 'ContextMenuContext'; - -export {ContextMenuContext, ContextMenuContextDefaultValue}; diff --git a/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/index.js b/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/index.js index 07f18814f3e8b..ccd2ee755583e 100644 --- a/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/index.js +++ b/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/index.js @@ -6,9 +6,8 @@ import { propTypes as ReportActionContextMenuPropsTypes, defaultProps as ReportActionContextMenuDefaultProps, } from '../ReportActionContextMenuPropsTypes'; -import withLocalize from '../../../../../components/withLocalize'; import {getMiniReportActionContextMenuWrapperStyle} from '../../../../../styles/getReportActionItemStyles'; -import ReportActionContextMenu from '../ReportActionContextMenu'; +import BaseReportActionContextMenu from '../BaseReportActionContextMenu'; const propTypes = { ..._.omit(ReportActionContextMenuPropsTypes, ['isMini']), @@ -25,7 +24,7 @@ const defaultProps = { const MiniReportActionContextMenu = props => ( {/* eslint-disable-next-line react/jsx-props-no-spreading */} - + ); @@ -33,4 +32,4 @@ MiniReportActionContextMenu.propTypes = propTypes; MiniReportActionContextMenu.defaultProps = defaultProps; MiniReportActionContextMenu.displayName = 'MiniReportActionContextMenu'; -export default withLocalize(MiniReportActionContextMenu); +export default MiniReportActionContextMenu; diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js index 39fca06fbec48..ad6d7b6aa2d9d 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js @@ -2,19 +2,16 @@ import React from 'react'; import { Dimensions, } from 'react-native'; -import PropTypes from 'prop-types'; import _ from 'underscore'; import { deleteReportComment, } from '../../../../libs/actions/Report'; import withLocalize, {withLocalizePropTypes} from '../../../../components/withLocalize'; import PopoverWithMeasuredContent from '../../../../components/PopoverWithMeasuredContent'; -import ReportActionContextMenu from './ReportActionContextMenu'; +import BaseReportActionContextMenu from './BaseReportActionContextMenu'; import ConfirmModal from '../../../../components/ConfirmModal'; const propTypes = { - /** Update the callbacks on ContextMenu Context */ - setValue: PropTypes.func.isRequired, ...withLocalizePropTypes, }; @@ -55,12 +52,6 @@ class PopoverReportActionContextMenu extends React.Component { } componentDidMount() { - this.props.setValue({ - showContextMenu: this.showContextMenu, - hideContextMenu: this.hideContextMenu, - isActiveReportAction: this.isActiveReportAction, - showDeleteConfirmModal: this.showDeleteConfirmModal, - }); Dimensions.addEventListener('change', this.measureContextMenuAnchorPosition); } @@ -181,7 +172,7 @@ class PopoverReportActionContextMenu extends React.Component { */ measureContent() { return ( - - { - const message = _.last(lodashGet(this.props.reportAction, 'message', null)); - const html = lodashGet(message, 'html', ''); - const text = Str.htmlDecode(props.selection || lodashGet(message, 'text', '')); - const isAttachment = _.has(this.props.reportAction, 'isAttachment') - ? this.props.reportAction.isAttachment - : isReportMessageAttachment(text); - if (!isAttachment) { - Clipboard.setString(text); - } else { - Clipboard.setString(html); - } - if (!this.props.isMini) { - this.hidePopover(true, ReportActionComposeFocusManager.focus); - } - }, - }, - - { - text: this.props.translate('reportActionContextMenu.copyLink'), - icon: LinkCopy, - shouldShow: false, - onPress: () => {}, - }, - - { - text: this.props.translate('reportActionContextMenu.markAsUnread'), - icon: Mail, - successIcon: Checkmark, - shouldShow: true, - onPress: () => { - updateLastReadActionID(this.props.reportID, this.props.reportAction.sequenceNumber); - setNewMarkerPosition(this.props.reportID, this.props.reportAction.sequenceNumber); - if (!this.props.isMini) { - this.hidePopover(true, ReportActionComposeFocusManager.focus); - } - }, - }, - - { - text: this.props.translate('reportActionContextMenu.editComment'), - icon: Pencil, - shouldShow: () => canEditReportAction(this.props.reportAction), - onPress: () => { - const editAction = () => saveReportActionDraft( - this.props.reportID, - this.props.reportAction.reportActionID, - _.isEmpty(this.props.draftMessage) ? this.getActionText() : '', - ); - - if (this.props.isMini) { - // No popover to hide, call editAction immediately - editAction(); - } else { - // Hide popover, then call editAction - this.hidePopover(false, editAction); - } - }, - }, - { - text: this.props.translate('reportActionContextMenu.deleteComment'), - icon: Trashcan, - shouldShow: () => canDeleteReportAction(this.props.reportAction), - onPress: () => { - if (this.props.isMini) { - // No popover to hide, call showDeleteConfirmModal immediately - this.props.showDeleteConfirmModal(this.props.reportID, this.props.reportAction); - } else { - // preserve the copy of props so that they don't reset on prop changes during hidePopover call - const reportID = this.props.reportID; - const reportAction = this.props.reportAction; - - // Hide popover, then call showDeleteConfirmModal - this.hidePopover( - false, - () => this.props.showDeleteConfirmModal(reportID, reportAction), - ); - } - }, - }, - ]; - - this.wrapperStyle = getReportActionContextMenuStyles(this.props.isMini); +/** + * Show the ReportActionContextMenu modal popover. + * + * @param {Object} [event] - A press event. + * @param {string} [selection] - A copy text. + * @param {Element} contextMenuAnchor - popoverAnchor + * @param {Number} reportID - Active Report Id + * @param {Object} reportAction - ReportAction for ContextMenu + * @param {String} draftMessage - ReportAction Draftmessage + * @param {Function} [onShown=() => {}] - Run a callback when Menu is shown + * @memberof PopoverReportActionContextMenu + */ +function showContextMenu(event, selection, contextMenuAnchor, reportID, reportAction, draftMessage, onShown = () => {}) { + if (!contextMenuRef.current) { + return; } + contextMenuRef.current.showContextMenu( + event, + selection, + contextMenuAnchor, + reportID, + reportAction, + draftMessage, + onShown, + ); +} - /** - * Gets the markdown version of the message in an action. - * - * @return {String} +/** + * Hide the ReportActionContextMenu modal popover. + * Hides the popover menu with an optional delay + * @param {Boolean} shouldDelay - whether the menu should close after a delay + * @param {Function} [onHideCallback=() => {}] - Callback to be called after Context Menu is completely hidden */ - getActionText() { - const message = _.last(lodashGet(this.props.reportAction, 'message', null)); - return lodashGet(message, 'html', ''); +function hideContextMenu(shouldDelay, onHideCallback = () => {}) { + if (!contextMenuRef.current) { + return; } + if (!shouldDelay) { + contextMenuRef.current.hideContextMenu(onHideCallback); - /** - * Hides the popover menu with an optional delay - * - * @param {Boolean} shouldDelay whether the menu should close after a delay - * @param {Function} [onHideCallback=() => {}] Callback to be called after Popover Menu is hidden - * @memberof ReportActionContextMenu - */ - hidePopover(shouldDelay, onHideCallback = () => {}) { - if (!shouldDelay) { - this.props.hidePopover(onHideCallback); - return; - } - setTimeout(() => this.props.hidePopover(onHideCallback), 800); + return; } + setTimeout(() => contextMenuRef.current.hideContextMenu(onHideCallback), 800); +} - render() { - return this.props.isVisible && ( - - {this.contextActions.map(contextAction => _.result(contextAction, 'shouldShow', false) && ( - contextAction.onPress(this.props.reportAction)} - /> - ))} - - ); +function hideDeleteConfirmModal() { + if (!contextMenuRef.current) { + return; } + contextMenuRef.current.hideDeleteConfirmModal(); } -ReportActionContextMenu.propTypes = propTypes; -ReportActionContextMenu.defaultProps = defaultProps; +/** + * Opens the Confirm delete action modal + * @param {Number} reportID + * @param {Object} reportAction + * @memberof PopoverReportActionContextMenu + */ +function showDeleteConfirmModal(reportID, reportAction) { + if (!contextMenuRef.current) { + return; + } + contextMenuRef.current.showDeleteConfirmModal(reportID, reportAction); +} +function isActiveReportAction(actionId) { + if (!contextMenuRef.current) { + return; + } + contextMenuRef.current.isActiveReportAction(actionId); +} -export default withLocalize(ReportActionContextMenu); +export { + contextMenuRef, + showContextMenu, + hideContextMenu, + isActiveReportAction, + showDeleteConfirmModal, + hideDeleteConfirmModal, +}; diff --git a/src/pages/home/report/ContextMenu/ReportActionContextMenuPropsTypes.js b/src/pages/home/report/ContextMenu/ReportActionContextMenuPropsTypes.js index caf6c4f1c1b60..302c2ca721395 100644 --- a/src/pages/home/report/ContextMenu/ReportActionContextMenuPropsTypes.js +++ b/src/pages/home/report/ContextMenu/ReportActionContextMenuPropsTypes.js @@ -23,12 +23,6 @@ const propTypes = { /** Draft message - if this is set the comment is in 'edit' mode */ draftMessage: PropTypes.string, - /** Function to show the delete Action confirmation modal */ - showDeleteConfirmModal: PropTypes.func.isRequired, - - /** Function to dismiss the popover containing this menu */ - hidePopover: PropTypes.func.isRequired, - ...withLocalizePropTypes, }; diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index b33921cda79dc..fcbcecd397104 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -23,7 +23,7 @@ import withWindowDimensions, {windowDimensionsPropTypes} from '../../../componen import ControlSelection from '../../../libs/ControlSelection'; import canUseTouchScreen from '../../../libs/canUseTouchscreen'; import MiniReportActionContextMenu from './ContextMenu/MiniReportActionContextMenu'; -import {ContextMenuContext} from './ContextMenu/ContextMenuContext'; +import {isActiveReportAction, showContextMenu} from './ContextMenu/ReportActionContextMenu'; const propTypes = { /** The ID of the report this action is on. */ @@ -64,6 +64,9 @@ class ReportActionItem extends Component { super(props); this.popoverAnchor = undefined; this.showPopover = this.showPopover.bind(this); + this.state = { + isContextMenuActive: isActiveReportAction(props.action.reportActionID), + }; } shouldComponentUpdate(nextProps) { @@ -86,7 +89,7 @@ class ReportActionItem extends Component { if (this.props.draftMessage) { return; } - this.context.showContextMenu( + showContextMenu( event, selection, this.popoverAnchor, @@ -94,8 +97,8 @@ class ReportActionItem extends Component { this.props.action, this.props.draftMessage, () => { - // ForceUpdate to hide the Mini menu when PopoverMenu is shown - this.forceUpdate(); + // Update to hide the Mini menu when PopoverMenu is shown + this.setState({isContextMenuActive: isActiveReportAction(this.props.action.reportActionID)}); }, ); } @@ -140,7 +143,7 @@ class ReportActionItem extends Component { @@ -162,13 +165,11 @@ class ReportActionItem extends Component { displayAsGroup={this.props.displayAsGroup} isVisible={ hovered - && !this.context.isActiveReportAction(this.props.action.reportActionID) + && !this.state.isContextMenuActive && !this.props.draftMessage } draftMessage={this.props.draftMessage} - hidePopover={this.context.hideContextMenu} - showDeleteConfirmModal={this.context.showDeleteConfirmModal} /> )} @@ -177,7 +178,6 @@ class ReportActionItem extends Component { ); } } -ReportActionItem.contextType = ContextMenuContext; ReportActionItem.propTypes = propTypes; ReportActionItem.defaultProps = defaultProps; diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index ebd3a3dfcd711..19161b742b567 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -34,7 +34,7 @@ import withDrawerState, {withDrawerPropTypes} from '../../../components/withDraw import {flatListRef, scrollToBottom} from '../../../libs/ReportScrollManager'; import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; import ReportActionComposeFocusManager from '../../../libs/ReportActionComposeFocusManager'; -import {ContextMenuContext, ContextMenuContextDefaultValue} from './ContextMenu/ContextMenuContext'; +import {contextMenuRef} from './ContextMenu/ReportActionContextMenu'; import PopoverReportActionContextMenu from './ContextMenu/PopoverReportActionContextMenu'; const propTypes = { @@ -98,13 +98,10 @@ class ReportActionsView extends React.Component { this.state = { isLoadingMoreChats: false, - contextMenuContext: ContextMenuContextDefaultValue, }; this.updateSortedReportActions(props.reportActions); this.updateMostRecentIOUReportActionNumber(props.reportActions); this.keyExtractor = this.keyExtractor.bind(this); - - this.updateContextMenuContext = this.updateContextMenuContext.bind(this); } componentDidMount() { @@ -410,7 +407,7 @@ class ReportActionsView extends React.Component { } return ( - + <> - - + + ); } } From 62b400e07bc0675f20990262355723ddc47dc279 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Wed, 28 Jul 2021 18:52:18 +0530 Subject: [PATCH 35/84] refactor and minor changes --- .../BaseReportActionContextMenu.js | 12 +++++-- ...enericReportActionContextMenuPropTypes.js} | 4 --- .../MiniReportActionContextMenu/index.js | 13 +++---- .../PopoverReportActionContextMenu.js | 34 ++++++++----------- 4 files changed, 32 insertions(+), 31 deletions(-) rename src/pages/home/report/ContextMenu/{ReportActionContextMenuPropsTypes.js => GenericReportActionContextMenuPropTypes.js} (86%) diff --git a/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.js b/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.js index 7fd9aabbfcd6d..37d7c4892e543 100755 --- a/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.js @@ -2,10 +2,18 @@ import React from 'react'; import {View} from 'react-native'; import getReportActionContextMenuStyles from '../../../../styles/getReportActionContextMenuStyles'; import ContextMenuItem from '../../../../components/ContextMenuItem'; -import {propTypes, defaultProps} from './ReportActionContextMenuPropsTypes'; -import withLocalize from '../../../../components/withLocalize'; +import { + propTypes as GenericReportActionContextMenuPropTypes, + defaultProps, +} from './GenericReportActionContextMenuPropTypes'; +import withLocalize, {withLocalizePropTypes} from '../../../../components/withLocalize'; import ContextMenuActions from './ContextMenuActions'; +const propTypes = { + ...GenericReportActionContextMenuPropTypes, + ...withLocalizePropTypes, +}; + class BaseReportActionContextMenu extends React.Component { constructor(props) { super(props); diff --git a/src/pages/home/report/ContextMenu/ReportActionContextMenuPropsTypes.js b/src/pages/home/report/ContextMenu/GenericReportActionContextMenuPropTypes.js similarity index 86% rename from src/pages/home/report/ContextMenu/ReportActionContextMenuPropsTypes.js rename to src/pages/home/report/ContextMenu/GenericReportActionContextMenuPropTypes.js index 302c2ca721395..ab21ecf8b7da8 100644 --- a/src/pages/home/report/ContextMenu/ReportActionContextMenuPropsTypes.js +++ b/src/pages/home/report/ContextMenu/GenericReportActionContextMenuPropTypes.js @@ -1,10 +1,8 @@ import PropTypes from 'prop-types'; -import {withLocalizePropTypes} from '../../../../components/withLocalize'; import ReportActionPropTypes from '../ReportActionPropTypes'; const propTypes = { /** The ID of the report this report action is attached to. */ - // eslint-disable-next-line react/no-unused-prop-types reportID: PropTypes.number.isRequired, /** The report action this context menu is attached to. */ @@ -22,8 +20,6 @@ const propTypes = { /** Draft message - if this is set the comment is in 'edit' mode */ draftMessage: PropTypes.string, - - ...withLocalizePropTypes, }; const defaultProps = { diff --git a/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/index.js b/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/index.js index ccd2ee755583e..a57825716bb77 100644 --- a/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/index.js +++ b/src/pages/home/report/ContextMenu/MiniReportActionContextMenu/index.js @@ -3,21 +3,22 @@ import React from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; import { - propTypes as ReportActionContextMenuPropsTypes, - defaultProps as ReportActionContextMenuDefaultProps, -} from '../ReportActionContextMenuPropsTypes'; + propTypes as GenericReportActionContextMenuPropTypes, + defaultProps as GenericReportActionContextMenuDefaultProps, +} from '../GenericReportActionContextMenuPropTypes'; import {getMiniReportActionContextMenuWrapperStyle} from '../../../../../styles/getReportActionItemStyles'; import BaseReportActionContextMenu from '../BaseReportActionContextMenu'; const propTypes = { - ..._.omit(ReportActionContextMenuPropsTypes, ['isMini']), + ..._.omit(GenericReportActionContextMenuPropTypes, ['isMini']), - /** Should the comment have the appearance of being grouped with the previous comment? */ + /** Should the reportAction this menu is attached to have the appearance of being + * grouped with the previous reportAction? */ displayAsGroup: PropTypes.bool, }; const defaultProps = { - ..._.omit(ReportActionContextMenuDefaultProps, ['isMini']), + ..._.omit(GenericReportActionContextMenuDefaultProps, ['isMini']), displayAsGroup: false, }; diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js index ad6d7b6aa2d9d..00f800e850b53 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js @@ -20,7 +20,7 @@ class PopoverReportActionContextMenu extends React.Component { super(props); this.state = { - reportID: 0, + reportID: null, reportAction: {}, selection: '', reportActionDraftMessage: '', @@ -38,15 +38,15 @@ class PopoverReportActionContextMenu extends React.Component { }, }; this.onPopoverHide = () => {}; - this.contextMenuAchor = undefined; + this.contextMenuAnchor = undefined; this.showContextMenu = this.showContextMenu.bind(this); this.hideContextMenu = this.hideContextMenu.bind(this); this.measureContent = this.measureContent.bind(this); this.measureContextMenuAnchorPosition = this.measureContextMenuAnchorPosition.bind(this); this.confirmDeleteAndHideModal = this.confirmDeleteAndHideModal.bind(this); - this.hideDeleteConfirmModal = this.hideDeleteConfirmModal.bind(this); - this.showDeleteConfirmModal = this.showDeleteConfirmModal.bind(this); - this.contextMenuHidden = this.contextMenuHidden.bind(this); + this.hideDeleteModal = this.hideDeleteModal.bind(this); + this.showDeleteModal = this.showDeleteModal.bind(this); + this.runAfterContextMenuHide = this.runAfterContextMenuHide.bind(this); this.getContextMenuMeasuredLocation = this.getContextMenuMeasuredLocation.bind(this); this.isActiveReportAction = this.isActiveReportAction.bind(this); } @@ -73,11 +73,11 @@ class PopoverReportActionContextMenu extends React.Component { * @memberof PopoverReportActionContextMenu */ getContextMenuMeasuredLocation() { - return new Promise((res) => { - if (this.contextMenuAchor) { - this.contextMenuAchor.measureInWindow((x, y) => res({x, y})); + return new Promise((resolve) => { + if (this.contextMenuAnchor) { + this.contextMenuAnchor.measureInWindow((x, y) => resolve({x, y})); } else { - res({x: 0, y: 0}); + resolve({x: 0, y: 0}); } }); } @@ -100,7 +100,7 @@ class PopoverReportActionContextMenu extends React.Component { */ showContextMenu(event, selection, contextMenuAnchor, reportID, reportAction, draftMessage, onShown = () => {}) { const nativeEvent = event.nativeEvent || {}; - this.contextMenuAchor = contextMenuAnchor; + this.contextMenuAnchor = contextMenuAnchor; this.getContextMenuMeasuredLocation().then(({x, y}) => { this.setState({ cursorRelativePosition: { @@ -140,7 +140,7 @@ class PopoverReportActionContextMenu extends React.Component { }); } - contextMenuHidden() { + runAfterContextMenuHide() { this.onPopoverHide(); // After we have called the action, reset it. @@ -177,8 +177,6 @@ class PopoverReportActionContextMenu extends React.Component { selection={this.state.selection} reportID={this.state.reportID} reportAction={this.state.reportAction} - hidePopover={this.hideContextMenu} - showDeleteConfirmModal={this.showDeleteConfirmModal} /> ); } @@ -188,7 +186,7 @@ class PopoverReportActionContextMenu extends React.Component { this.setState({isDeleteCommentConfirmModalVisible: false}); } - hideDeleteConfirmModal() { + hideDeleteModal() { this.setState({ reportID: 0, reportAction: {}, @@ -202,7 +200,7 @@ class PopoverReportActionContextMenu extends React.Component { * @param {Object} reportAction * @memberof PopoverReportActionContextMenu */ - showDeleteConfirmModal(reportID, reportAction) { + showDeleteModal(reportID, reportAction) { this.setState({reportID, reportAction, isDeleteCommentConfirmModalVisible: true}); } @@ -212,7 +210,7 @@ class PopoverReportActionContextMenu extends React.Component { Date: Wed, 28 Jul 2021 19:07:06 +0530 Subject: [PATCH 36/84] Rename and fix errors --- src/components/PopoverWithMeasuredContent.js | 10 +++-- .../report/ContextMenu/ContextMenuActions.js | 6 +-- .../PopoverReportActionContextMenu.js | 10 ++--- .../ContextMenu/ReportActionContextMenu.js | 40 +++++++++++-------- 4 files changed, 37 insertions(+), 29 deletions(-) diff --git a/src/components/PopoverWithMeasuredContent.js b/src/components/PopoverWithMeasuredContent.js index 6949859074ef8..620cd463ef85c 100644 --- a/src/components/PopoverWithMeasuredContent.js +++ b/src/components/PopoverWithMeasuredContent.js @@ -61,6 +61,7 @@ class PopoverWithMeasuredContent extends Component { this.popoverHeight = 0; this.measurePopover = this.measurePopover.bind(this); + this.setContentMeasured = this.setContentMeasured.bind(this); } shouldComponentUpdate(nextProps, nextState) { @@ -81,11 +82,14 @@ class PopoverWithMeasuredContent extends Component { componentDidUpdate(prevProps) { // When Popover is shown recalculate if (!prevProps.isVisible && this.props.isVisible) { - // eslint-disable-next-line react/no-did-update-set-state - this.setState({isContentMeasured: false}); + this.setContentMeasured(false); } } + setContentMeasured(isContentMeasured) { + this.setState({isContentMeasured}); + } + /** * Measure the size of the popover's content. * @@ -94,7 +98,7 @@ class PopoverWithMeasuredContent extends Component { measurePopover({nativeEvent}) { this.popoverWidth = nativeEvent.layout.width; this.popoverHeight = nativeEvent.layout.height; - this.setState({isContentMeasured: true}); + this.setContentMeasured(true); } /** diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js index 1b0c1f2dd57bc..0cd8e2784744d 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.js +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js @@ -10,7 +10,7 @@ import { import Clipboard from '../../../../libs/Clipboard'; import {isReportMessageAttachment, canEditReportAction, canDeleteReportAction} from '../../../../libs/reportUtils'; import ReportActionComposeFocusManager from '../../../../libs/ReportActionComposeFocusManager'; -import {hideContextMenu, showDeleteConfirmModal} from './ReportActionContextMenu'; +import {hideContextMenu, showDeleteModal} from './ReportActionContextMenu'; /** * Gets the markdown version of the message in an action. @@ -104,13 +104,13 @@ export default [ // Hide popover, then call showDeleteConfirmModal hideContextMenu( false, - () => showDeleteConfirmModal(reportID, reportAction), + () => showDeleteModal(reportID, reportAction), ); return; } // No popover to hide, call showDeleteConfirmModal immediately - showDeleteConfirmModal(reportID, reportAction); + showDeleteModal(reportID, reportAction); }, }, ]; diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js index 00f800e850b53..3da3d7eb23d89 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js @@ -20,7 +20,7 @@ class PopoverReportActionContextMenu extends React.Component { super(props); this.state = { - reportID: null, + reportID: 0, reportAction: {}, selection: '', reportActionDraftMessage: '', @@ -70,7 +70,6 @@ class PopoverReportActionContextMenu extends React.Component { * We calculate the achor coordinates from measureInWindow async method * * @returns {Promise} - * @memberof PopoverReportActionContextMenu */ getContextMenuMeasuredLocation() { return new Promise((resolve) => { @@ -82,8 +81,8 @@ class PopoverReportActionContextMenu extends React.Component { }); } - isActiveReportAction(actionId) { - return this.state.reportAction.reportActionID === actionId; + isActiveReportAction(actionID) { + return this.state.reportAction.reportActionID === actionID; } /** @@ -96,7 +95,6 @@ class PopoverReportActionContextMenu extends React.Component { * @param {Object} reportAction - ReportAction for ContextMenu * @param {String} draftMessage - ReportAction Draftmessage * @param {Function} [onShown=() => {}] - Run a callback when Menu is shown - * @memberof PopoverReportActionContextMenu */ showContextMenu(event, selection, contextMenuAnchor, reportID, reportAction, draftMessage, onShown = () => {}) { const nativeEvent = event.nativeEvent || {}; @@ -168,7 +166,6 @@ class PopoverReportActionContextMenu extends React.Component { * Used to calculate the Context Menu Dimensions * * @returns {JSX} - * @memberof PopoverReportActionContextMenu */ measureContent() { return ( @@ -198,7 +195,6 @@ class PopoverReportActionContextMenu extends React.Component { * Opens the Confirm delete action modal * @param {Number} reportID * @param {Object} reportAction - * @memberof PopoverReportActionContextMenu */ showDeleteModal(reportID, reportAction) { this.setState({reportID, reportAction, isDeleteCommentConfirmModalVisible: true}); diff --git a/src/pages/home/report/ContextMenu/ReportActionContextMenu.js b/src/pages/home/report/ContextMenu/ReportActionContextMenu.js index cdd8cc87d744c..93e2c7480d88a 100644 --- a/src/pages/home/report/ContextMenu/ReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/ReportActionContextMenu.js @@ -12,9 +12,16 @@ const contextMenuRef = React.createRef(); * @param {Object} reportAction - ReportAction for ContextMenu * @param {String} draftMessage - ReportAction Draftmessage * @param {Function} [onShown=() => {}] - Run a callback when Menu is shown - * @memberof PopoverReportActionContextMenu */ -function showContextMenu(event, selection, contextMenuAnchor, reportID, reportAction, draftMessage, onShown = () => {}) { +function showContextMenu( + event, + selection, + contextMenuAnchor, + reportID, + reportAction, + draftMessage, + onShown = () => {}, +) { if (!contextMenuRef.current) { return; } @@ -30,11 +37,11 @@ function showContextMenu(event, selection, contextMenuAnchor, reportID, reportAc } /** - * Hide the ReportActionContextMenu modal popover. - * Hides the popover menu with an optional delay - * @param {Boolean} shouldDelay - whether the menu should close after a delay - * @param {Function} [onHideCallback=() => {}] - Callback to be called after Context Menu is completely hidden - */ + * Hide the ReportActionContextMenu modal popover. + * Hides the popover menu with an optional delay + * @param {Boolean} shouldDelay - whether the menu should close after a delay + * @param {Function} [onHideCallback=() => {}] - Callback to be called after Context Menu is completely hidden + */ function hideContextMenu(shouldDelay, onHideCallback = () => {}) { if (!contextMenuRef.current) { return; @@ -47,30 +54,31 @@ function hideContextMenu(shouldDelay, onHideCallback = () => {}) { setTimeout(() => contextMenuRef.current.hideContextMenu(onHideCallback), 800); } -function hideDeleteConfirmModal() { +function hideDeleteModal() { if (!contextMenuRef.current) { return; } - contextMenuRef.current.hideDeleteConfirmModal(); + contextMenuRef.current.hideDeleteModal(); } /** * Opens the Confirm delete action modal * @param {Number} reportID * @param {Object} reportAction - * @memberof PopoverReportActionContextMenu */ -function showDeleteConfirmModal(reportID, reportAction) { +function showDeleteModal(reportID, reportAction) { if (!contextMenuRef.current) { return; } - contextMenuRef.current.showDeleteConfirmModal(reportID, reportAction); + console.debug(reportID, reportAction); + contextMenuRef.current.showDeleteModal(reportID, reportAction); } -function isActiveReportAction(actionId) { + +function isActiveReportAction(actionID) { if (!contextMenuRef.current) { return; } - contextMenuRef.current.isActiveReportAction(actionId); + contextMenuRef.current.isActiveReportAction(actionID); } export { @@ -78,6 +86,6 @@ export { showContextMenu, hideContextMenu, isActiveReportAction, - showDeleteConfirmModal, - hideDeleteConfirmModal, + showDeleteModal, + hideDeleteModal, }; From a9642701b64cafc131b64f6b27a75167eb0a264a Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Wed, 28 Jul 2021 21:04:30 +0530 Subject: [PATCH 37/84] fix: Highlight issue when contextMenu toggle between open and close state --- src/components/PopoverWithMeasuredContent.js | 20 ++++++++++++------- .../PopoverReportActionContextMenu.js | 19 +++++++++++++++--- .../ContextMenu/ReportActionContextMenu.js | 19 +++++++++++++----- src/pages/home/report/ReportActionItem.js | 18 ++++++++++------- src/pages/home/report/ReportActionsView.js | 6 ++++++ 5 files changed, 60 insertions(+), 22 deletions(-) diff --git a/src/components/PopoverWithMeasuredContent.js b/src/components/PopoverWithMeasuredContent.js index 620cd463ef85c..81839053bc425 100644 --- a/src/components/PopoverWithMeasuredContent.js +++ b/src/components/PopoverWithMeasuredContent.js @@ -55,6 +55,7 @@ class PopoverWithMeasuredContent extends Component { this.state = { isContentMeasured: false, + isVisible: false, }; this.popoverWidth = 0; @@ -64,6 +65,17 @@ class PopoverWithMeasuredContent extends Component { this.setContentMeasured = this.setContentMeasured.bind(this); } + static getDerivedStateFromProps(props, state) { + // When Popover is shown recalculate + if (!state.isVisible && props.isVisible) { + return {isContentMeasured: false, isVisible: true}; + } + if (!props.isVisible) { + return {isVisible: false}; + } + return null; + } + shouldComponentUpdate(nextProps, nextState) { if (this.props.isVisible && (nextProps.windowWidth !== this.props.windowWidth @@ -79,13 +91,6 @@ class PopoverWithMeasuredContent extends Component { ) || !_.isEqual(this.state, nextState); } - componentDidUpdate(prevProps) { - // When Popover is shown recalculate - if (!prevProps.isVisible && this.props.isVisible) { - this.setContentMeasured(false); - } - } - setContentMeasured(isContentMeasured) { this.setState({isContentMeasured}); } @@ -144,6 +149,7 @@ class PopoverWithMeasuredContent extends Component { } render() { + console.debug('render'); const adjustedAnchorPosition = this.calculateAdjustedAnchorPosition(); const horizontalShift = computeHorizontalShift( adjustedAnchorPosition.left, diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js index 3da3d7eb23d89..10862715b9c39 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js @@ -94,11 +94,22 @@ class PopoverReportActionContextMenu extends React.Component { * @param {Number} reportID - Active Report Id * @param {Object} reportAction - ReportAction for ContextMenu * @param {String} draftMessage - ReportAction Draftmessage - * @param {Function} [onShown=() => {}] - Run a callback when Menu is shown + * @param {Function} [onShow=() => {}] - Run a callback when Menu is shown + * @param {Function} [onHide=() => {}] - Run a callback when Menu is hidden */ - showContextMenu(event, selection, contextMenuAnchor, reportID, reportAction, draftMessage, onShown = () => {}) { + showContextMenu( + event, + selection, + contextMenuAnchor, + reportID, + reportAction, + draftMessage, + onShow = () => {}, + onHide = () => {}, + ) { const nativeEvent = event.nativeEvent || {}; this.contextMenuAnchor = contextMenuAnchor; + this.onPopoverHide = onHide; this.getContextMenuMeasuredLocation().then(({x, y}) => { this.setState({ cursorRelativePosition: { @@ -114,7 +125,7 @@ class PopoverReportActionContextMenu extends React.Component { selection, isPopoverVisible: true, reportActionDraftMessage: draftMessage, - }, onShown); + }, onShow); }); } @@ -139,6 +150,7 @@ class PopoverReportActionContextMenu extends React.Component { } runAfterContextMenuHide() { + console.debug('callback'); this.onPopoverHide(); // After we have called the action, reset it. @@ -160,6 +172,7 @@ class PopoverReportActionContextMenu extends React.Component { reportActionDraftMessage: '', isPopoverVisible: false, }); + console.debug('reset state'); } /** diff --git a/src/pages/home/report/ContextMenu/ReportActionContextMenu.js b/src/pages/home/report/ContextMenu/ReportActionContextMenu.js index 93e2c7480d88a..197dc2daac115 100644 --- a/src/pages/home/report/ContextMenu/ReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/ReportActionContextMenu.js @@ -11,7 +11,8 @@ const contextMenuRef = React.createRef(); * @param {Number} reportID - Active Report Id * @param {Object} reportAction - ReportAction for ContextMenu * @param {String} draftMessage - ReportAction Draftmessage - * @param {Function} [onShown=() => {}] - Run a callback when Menu is shown + * @param {Function} [onShow=() => {}] - Run a callback when Menu is shown + * @param {Function} [onHide=() => {}] - Run a callback when Menu is hidden */ function showContextMenu( event, @@ -20,7 +21,8 @@ function showContextMenu( reportID, reportAction, draftMessage, - onShown = () => {}, + onShow = () => {}, + onHide = () => {}, ) { if (!contextMenuRef.current) { return; @@ -32,7 +34,8 @@ function showContextMenu( reportID, reportAction, draftMessage, - onShown, + onShow, + onHide, ); } @@ -58,7 +61,7 @@ function hideDeleteModal() { if (!contextMenuRef.current) { return; } - contextMenuRef.current.hideDeleteModal(); + return contextMenuRef.current.hideDeleteModal(); } /** @@ -74,11 +77,17 @@ function showDeleteModal(reportID, reportAction) { contextMenuRef.current.showDeleteModal(reportID, reportAction); } +/** + * Whether Context Menu is active for the Report Action. + * + * @param {Number|String} actionID + * @return {Boolean} + */ function isActiveReportAction(actionID) { if (!contextMenuRef.current) { return; } - contextMenuRef.current.isActiveReportAction(actionID); + return contextMenuRef.current.isActiveReportAction(actionID); } export { diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index fcbcecd397104..2079b5d9c40c8 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -63,19 +63,21 @@ class ReportActionItem extends Component { constructor(props) { super(props); this.popoverAnchor = undefined; - this.showPopover = this.showPopover.bind(this); this.state = { isContextMenuActive: isActiveReportAction(props.action.reportActionID), }; + this.checkIfContextMenuActive = this.checkIfContextMenuActive.bind(this); + this.showPopover = this.showPopover.bind(this); } - shouldComponentUpdate(nextProps) { + shouldComponentUpdate(nextProps, nextState) { return this.props.displayAsGroup !== nextProps.displayAsGroup || this.props.draftMessage !== nextProps.draftMessage || this.props.isMostRecentIOUReportAction !== nextProps.isMostRecentIOUReportAction || this.props.hasOutstandingIOU !== nextProps.hasOutstandingIOU || this.props.shouldDisplayNewIndicator !== nextProps.shouldDisplayNewIndicator - || !_.isEqual(this.props.action, nextProps.action); + || !_.isEqual(this.props.action, nextProps.action) + || this.state.isContextMenuActive !== nextState.isContextMenuActive; } /** @@ -96,13 +98,15 @@ class ReportActionItem extends Component { this.props.reportID, this.props.action, this.props.draftMessage, - () => { - // Update to hide the Mini menu when PopoverMenu is shown - this.setState({isContextMenuActive: isActiveReportAction(this.props.action.reportActionID)}); - }, + this.checkIfContextMenuActive, + this.checkIfContextMenuActive, ); } + checkIfContextMenuActive() { + this.setState({isContextMenuActive: isActiveReportAction(this.props.action.reportActionID)}); + } + render() { let children; if (this.props.action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU) { diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index 19161b742b567..4827f745b4e8d 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -222,6 +222,12 @@ class ReportActionsView extends React.Component { this.setState({contextMenuContext: value}); } + /** + * Create a unique key for Each Action in the FlatList. + * + * @param {Object} item + * @return {String} + */ keyExtractor(item) { return `${item.action.sequenceNumber}${item.action.clientID}`; } From 498b558ea871f2fd2bd3f47d266a313fbb25e21a Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Wed, 28 Jul 2021 21:09:30 +0530 Subject: [PATCH 38/84] clean up --- src/components/PopoverWithMeasuredContent.js | 1 - .../home/report/ContextMenu/PopoverReportActionContextMenu.js | 2 -- src/pages/home/report/ReportActionItem.js | 3 --- 3 files changed, 6 deletions(-) diff --git a/src/components/PopoverWithMeasuredContent.js b/src/components/PopoverWithMeasuredContent.js index 81839053bc425..f17c7fd17cf0d 100644 --- a/src/components/PopoverWithMeasuredContent.js +++ b/src/components/PopoverWithMeasuredContent.js @@ -149,7 +149,6 @@ class PopoverWithMeasuredContent extends Component { } render() { - console.debug('render'); const adjustedAnchorPosition = this.calculateAdjustedAnchorPosition(); const horizontalShift = computeHorizontalShift( adjustedAnchorPosition.left, diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js index 10862715b9c39..98985bce21490 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js @@ -150,7 +150,6 @@ class PopoverReportActionContextMenu extends React.Component { } runAfterContextMenuHide() { - console.debug('callback'); this.onPopoverHide(); // After we have called the action, reset it. @@ -172,7 +171,6 @@ class PopoverReportActionContextMenu extends React.Component { reportActionDraftMessage: '', isPopoverVisible: false, }); - console.debug('reset state'); } /** diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 2079b5d9c40c8..010f9e2998172 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -18,7 +18,6 @@ import ReportActionItemMessage from './ReportActionItemMessage'; import UnreadActionIndicator from '../../../components/UnreadActionIndicator'; import ReportActionItemMessageEdit from './ReportActionItemMessageEdit'; import compose from '../../../libs/compose'; -import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions'; import ControlSelection from '../../../libs/ControlSelection'; import canUseTouchScreen from '../../../libs/canUseTouchscreen'; @@ -50,7 +49,6 @@ const propTypes = { /** Draft message - if this is set the comment is in 'edit' mode */ draftMessage: PropTypes.string, - ...withLocalizePropTypes, ...windowDimensionsPropTypes, }; @@ -187,7 +185,6 @@ ReportActionItem.defaultProps = defaultProps; export default compose( withWindowDimensions, - withLocalize, withOnyx({ draftMessage: { key: ({ From f1a2def37cf5a7ff6561f037976442a18851a3e9 Mon Sep 17 00:00:00 2001 From: Jasper Huang Date: Wed, 28 Jul 2021 09:13:43 -0700 Subject: [PATCH 39/84] Disable the submit button if required form fields are not filled out --- src/pages/ReimbursementAccount/CompanyStep.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/pages/ReimbursementAccount/CompanyStep.js b/src/pages/ReimbursementAccount/CompanyStep.js index 5f8732b881efa..87c2e167c4925 100644 --- a/src/pages/ReimbursementAccount/CompanyStep.js +++ b/src/pages/ReimbursementAccount/CompanyStep.js @@ -45,6 +45,19 @@ class CompanyStep extends React.Component { hasNoConnectionToCannabis: lodashGet(props, ['achData', 'hasNoConnectionToCannabis'], false), password: '', }; + + // These fields need to be filled out in order to submit the form + this.requiredFields = [ + 'companyName', + 'addressStreet', + 'addressCity', + 'addressState', + 'addressZipCode', + 'companyTaxID', + 'incorporationDate', + 'industryCode', + 'password', + ]; } /** @@ -111,6 +124,8 @@ class CompanyStep extends React.Component { render() { const shouldDisableCompanyName = Boolean(this.props.achData.bankAccountID && this.props.achData.companyName); const shouldDisableCompanyTaxID = Boolean(this.props.achData.bankAccountID && this.props.achData.companyTaxID); + const shouldDisableSubmitButton = this.requiredFields + .reduce((acc, curr) => acc || !this.state[curr].trim(), false); return ( <> From 3b545d01cd4b5591a537137dd4a166d34a8b4809 Mon Sep 17 00:00:00 2001 From: Jasper Huang Date: Wed, 28 Jul 2021 09:19:40 -0700 Subject: [PATCH 40/84] Add website --- src/pages/ReimbursementAccount/CompanyStep.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/ReimbursementAccount/CompanyStep.js b/src/pages/ReimbursementAccount/CompanyStep.js index 87c2e167c4925..c0fc39effba9b 100644 --- a/src/pages/ReimbursementAccount/CompanyStep.js +++ b/src/pages/ReimbursementAccount/CompanyStep.js @@ -53,6 +53,7 @@ class CompanyStep extends React.Component { 'addressCity', 'addressState', 'addressZipCode', + 'website', 'companyTaxID', 'incorporationDate', 'industryCode', From 2dca887194048227f7ef448c66333981e430af1d Mon Sep 17 00:00:00 2001 From: Aman Ansari Date: Wed, 28 Jul 2021 21:50:32 +0530 Subject: [PATCH 41/84] Use proper width for Cancel/Decline Button Text --- src/components/ReportTransaction.js | 2 +- src/styles/utilities/sizing.js | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/components/ReportTransaction.js b/src/components/ReportTransaction.js index 197e2fedb812c..97230935f1151 100644 --- a/src/components/ReportTransaction.js +++ b/src/components/ReportTransaction.js @@ -93,7 +93,7 @@ class ReportTransaction extends Component { styles.buttonSmall, styles.chatItemComposeSecondaryRowOffset, styles.mb3, - styles.w20, + styles.wAuto, ]} onPress={this.rejectTransaction} > diff --git a/src/styles/utilities/sizing.js b/src/styles/utilities/sizing.js index 9724e9acf7989..1dbb4bd4c4500 100644 --- a/src/styles/utilities/sizing.js +++ b/src/styles/utilities/sizing.js @@ -16,10 +16,6 @@ export default { width: '50%', }, - w20: { - width: '20%', - }, - mwn: { maxWidth: 'auto', }, From 8b291cc563bc3274e75247066c0879c72f8d7ef6 Mon Sep 17 00:00:00 2001 From: Jasper Huang Date: Wed, 28 Jul 2021 09:46:39 -0700 Subject: [PATCH 42/84] Remove duplicate checks --- src/pages/ReimbursementAccount/CompanyStep.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/pages/ReimbursementAccount/CompanyStep.js b/src/pages/ReimbursementAccount/CompanyStep.js index c0fc39effba9b..47b6b46b4ba88 100644 --- a/src/pages/ReimbursementAccount/CompanyStep.js +++ b/src/pages/ReimbursementAccount/CompanyStep.js @@ -65,21 +65,11 @@ class CompanyStep extends React.Component { * @returns {Boolean} */ validate() { - if (!this.state.password.trim()) { - Growl.error(this.props.translate('common.passwordCannotBeBlank')); - return false; - } - if (!isValidAddress(this.state.addressStreet)) { Growl.error(this.props.translate('bankAccount.error.addressStreet')); return false; } - if (this.state.addressState === '') { - Growl.error(this.props.translate('bankAccount.error.addressState')); - return false; - } - if (!isValidZipCode(this.state.addressZipCode)) { Growl.error(this.props.translate('bankAccount.error.zipCode')); return false; From 2bb1e3f8a59300d05f826862b2cc9f21458d2dbe Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Wed, 28 Jul 2021 22:25:01 +0530 Subject: [PATCH 43/84] extra code revomed --- src/components/PopoverWithMeasuredContent.js | 6 +----- src/pages/home/report/ReportActionsView.js | 9 +++------ 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/components/PopoverWithMeasuredContent.js b/src/components/PopoverWithMeasuredContent.js index f17c7fd17cf0d..a370688c0ab79 100644 --- a/src/components/PopoverWithMeasuredContent.js +++ b/src/components/PopoverWithMeasuredContent.js @@ -91,10 +91,6 @@ class PopoverWithMeasuredContent extends Component { ) || !_.isEqual(this.state, nextState); } - setContentMeasured(isContentMeasured) { - this.setState({isContentMeasured}); - } - /** * Measure the size of the popover's content. * @@ -103,7 +99,7 @@ class PopoverWithMeasuredContent extends Component { measurePopover({nativeEvent}) { this.popoverWidth = nativeEvent.layout.width; this.popoverHeight = nativeEvent.layout.height; - this.setContentMeasured(true); + this.setState({isContentMeasured: true}); } /** diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index 4827f745b4e8d..49b8c1e1e9d44 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -99,6 +99,7 @@ class ReportActionsView extends React.Component { this.state = { isLoadingMoreChats: false, }; + this.updateSortedReportActions(props.reportActions); this.updateMostRecentIOUReportActionNumber(props.reportActions); this.keyExtractor = this.keyExtractor.bind(this); @@ -162,8 +163,7 @@ class ReportActionsView extends React.Component { return true; } - // ContextMenu props check - return this.state.contextMenuContext !== nextState.contextMenuContext; + return false; } componentDidUpdate(prevProps) { @@ -206,6 +206,7 @@ class ReportActionsView extends React.Component { } AppState.removeEventListener('change', this.onVisibilityChange); + unsubscribeFromReportChannel(this.props.reportID); } @@ -218,10 +219,6 @@ class ReportActionsView extends React.Component { } } - updateContextMenuContext(value) { - this.setState({contextMenuContext: value}); - } - /** * Create a unique key for Each Action in the FlatList. * From baf1d297e68c3f5ef887e7ee7d4ef364d55e2131 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Wed, 28 Jul 2021 22:55:44 +0530 Subject: [PATCH 44/84] break fix --- src/components/PopoverWithMeasuredContent.js | 1 - .../home/report/ContextMenu/BaseReportActionContextMenu.js | 3 ++- src/pages/home/report/ContextMenu/ReportActionContextMenu.js | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/PopoverWithMeasuredContent.js b/src/components/PopoverWithMeasuredContent.js index a370688c0ab79..adb6498d71ffb 100644 --- a/src/components/PopoverWithMeasuredContent.js +++ b/src/components/PopoverWithMeasuredContent.js @@ -62,7 +62,6 @@ class PopoverWithMeasuredContent extends Component { this.popoverHeight = 0; this.measurePopover = this.measurePopover.bind(this); - this.setContentMeasured = this.setContentMeasured.bind(this); } static getDerivedStateFromProps(props, state) { diff --git a/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.js b/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.js index 37d7c4892e543..4f6b0dcf5ae60 100755 --- a/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.js @@ -1,5 +1,6 @@ import React from 'react'; import {View} from 'react-native'; +import _ from 'underscore'; import getReportActionContextMenuStyles from '../../../../styles/getReportActionContextMenuStyles'; import ContextMenuItem from '../../../../components/ContextMenuItem'; import { @@ -24,7 +25,7 @@ class BaseReportActionContextMenu extends React.Component { render() { return this.props.isVisible && ( - {ContextMenuActions.map(contextAction => contextAction.shouldShow(this.props.reportAction) && ( + {_.map(ContextMenuActions, contextAction => contextAction.shouldShow(this.props.reportAction) && ( Date: Wed, 28 Jul 2021 12:36:52 -0600 Subject: [PATCH 45/84] Un-capitalize --- src/languages/es.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/languages/es.js b/src/languages/es.js index f64ab3df8e510..6f34b545cbc4d 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -231,8 +231,8 @@ export default { enterYourUsernameToGetPaidViaPayPal: 'Escribe tu nombre de usuario para que otros puedan pagarte a través de PayPal.', payPalMe: 'PayPal.me/', yourPayPalUsername: 'Tu usuario de PayPal', - addPayPalAccount: 'Agregar Cuenta de PayPal', - editPayPalAccount: 'Actualizar Cuenta de PayPal', + addPayPalAccount: 'Agregar cuenta de PayPal', + editPayPalAccount: 'Actualizar cuenta de PayPal', }, paymentsPage: { paymentMethodsTitle: 'Métodos de pago', From b1c6da32d59b31a8b4986d49fb2c70084e532248 Mon Sep 17 00:00:00 2001 From: Jasper Huang Date: Wed, 28 Jul 2021 11:40:32 -0700 Subject: [PATCH 46/84] remove flex --- src/pages/ReimbursementAccount/BankAccountStep.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/ReimbursementAccount/BankAccountStep.js b/src/pages/ReimbursementAccount/BankAccountStep.js index 33aeb1edb4b61..b38563acdef00 100644 --- a/src/pages/ReimbursementAccount/BankAccountStep.js +++ b/src/pages/ReimbursementAccount/BankAccountStep.js @@ -253,7 +253,7 @@ class BankAccountStep extends React.Component { onConfirm={hideExistingOwnersError} shouldShowCancelButton={false} prompt={( - + {this.props.translate('bankAccount.error.existingOwners.alreadyInUse')} From 347f33e73e319238eb5f41c8bb0cebed3e988d98 Mon Sep 17 00:00:00 2001 From: Aman Ansari Date: Thu, 29 Jul 2021 02:55:05 +0530 Subject: [PATCH 47/84] update time when required, remove unnecessary logic, use preferredLocale --- src/pages/home/report/ParticipantLocalTime.js | 49 +++++++++---------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/src/pages/home/report/ParticipantLocalTime.js b/src/pages/home/report/ParticipantLocalTime.js index cfd902da46e4a..f3c297a5d55e2 100644 --- a/src/pages/home/report/ParticipantLocalTime.js +++ b/src/pages/home/report/ParticipantLocalTime.js @@ -35,6 +35,10 @@ class ParticipantLocalTime extends React.Component { }, 1000)); } + shouldComponentUpdate(nextProps, nextState) { + return nextState.localTime !== this.state.localTime; + } + componentWillUnmount() { clearInterval(this.timer); clearInterval(this.readyTimer); @@ -42,6 +46,7 @@ class ParticipantLocalTime extends React.Component { getParticipantLocalTime() { const reportRecipientTimezone = lodashGet(this.props.participant, 'timezone', {}); + moment.locale(this.props.preferredLocale); const reportRecipientDay = moment().tz(reportRecipientTimezone.selected).format('dddd'); const currentUserDay = moment().tz(this.props.currentUserTimezone.selected).format('dddd'); @@ -52,38 +57,32 @@ class ParticipantLocalTime extends React.Component { } render() { - // Moment.format does not return AM or PM values immediately. - // So we have to wait until we are ready before showing the time to the user - const isReportRecipientLocalTimeReady = this.state.localTime.toString().match(/(A|P)M/ig); const reportRecipientDisplayName = this.props.participant.firstName || (Str.isSMSLogin(this.props.participant.login) ? this.props.toLocalPhone(this.props.participant.displayName) : this.props.participant.displayName); return ( - isReportRecipientLocalTimeReady ? ( - - - - {this.props.translate('common.timePrefix')} - - - {this.state.localTime} - - - {this.props.translate('common.conjunctionFor')} - - - {reportRecipientDisplayName} - - + + + + {this.props.translate('common.timePrefix')} + + + {this.state.localTime} + + + {this.props.translate('common.conjunctionFor')} + + + {reportRecipientDisplayName} + - ) - : + ); } } From 660a61684d44894af506bf639403ab2c2d6ff980 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Thu, 29 Jul 2021 03:00:50 +0530 Subject: [PATCH 48/84] fix styling for report typing and time indicators --- src/languages/en.js | 3 +- src/languages/es.js | 3 +- src/pages/home/report/ParticipantLocalTime.js | 31 ++++++------- .../home/report/ReportTypingIndicator.js | 46 +++++++++---------- src/styles/styles.js | 8 ---- 5 files changed, 38 insertions(+), 53 deletions(-) diff --git a/src/languages/en.js b/src/languages/en.js index 0d38c1bcf1025..69444aa5f571f 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -58,8 +58,6 @@ export default { send: 'Send', notifications: 'Notifications', noResultsFound: 'No results found', - timePrefix: 'It\'s', - conjunctionFor: 'for', }, attachmentPicker: { cameraPermissionRequired: 'Camera Permission Required', @@ -110,6 +108,7 @@ export default { youAppearToBeOffline: 'You appear to be offline.', fileUploadFailed: 'Upload Failed. File is not supported.', roomIsArchived: 'This chat room has been deleted', + localTime: ({user, time}) => `It's ${time} for ${user}`, }, contextMenuItem: { copyToClipboard: 'Copy to Clipboard', diff --git a/src/languages/es.js b/src/languages/es.js index 6742589880661..cdea95005c935 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -53,8 +53,6 @@ export default { send: 'Enviar', notifications: 'Notificaciones', noResultsFound: 'No se han encontrado resultados', - timePrefix: 'Son las', - conjunctionFor: 'para', }, attachmentPicker: { cameraPermissionRequired: 'Se necesita permiso para usar la cámara', @@ -104,6 +102,7 @@ export default { blockedFromConcierge: 'Comunicación no permitida', youAppearToBeOffline: 'Parece que estás desconectado.', roomIsArchived: 'Esta sala de chat ha sido eliminada', + localTime: ({user, time}) => `Son las ${time} para ${user}`, }, reportActionContextMenu: { copyToClipboard: 'Copiar al Portapapeles', diff --git a/src/pages/home/report/ParticipantLocalTime.js b/src/pages/home/report/ParticipantLocalTime.js index cfd902da46e4a..3989d62cac9bf 100644 --- a/src/pages/home/report/ParticipantLocalTime.js +++ b/src/pages/home/report/ParticipantLocalTime.js @@ -63,24 +63,21 @@ class ParticipantLocalTime extends React.Component { return ( isReportRecipientLocalTimeReady ? ( - - - {this.props.translate('common.timePrefix')} - - - {this.state.localTime} - - - {this.props.translate('common.conjunctionFor')} - - - {reportRecipientDisplayName} - - + {this.props.translate( + 'reportActionCompose.localTime', + { + user: reportRecipientDisplayName, + time: this.state.localTime, + }, + )} + ) : diff --git a/src/pages/home/report/ReportTypingIndicator.js b/src/pages/home/report/ReportTypingIndicator.js index d12fdd35d032a..9e25d5627483d 100755 --- a/src/pages/home/report/ReportTypingIndicator.js +++ b/src/pages/home/report/ReportTypingIndicator.js @@ -54,14 +54,14 @@ class ReportTypingIndicator extends React.Component { case 1: return ( - - - {getDisplayName(this.state.usersTyping[0])} - + {getDisplayName(this.state.usersTyping[0])} {` ${this.props.translate('reportTypingIndicator.isTyping')}`} @@ -69,18 +69,16 @@ class ReportTypingIndicator extends React.Component { case 2: return ( - - - {getDisplayName(this.state.usersTyping[0])} - + {getDisplayName(this.state.usersTyping[0])} {` ${this.props.translate('common.and')} `} - - {getDisplayName(this.state.usersTyping[1])} - + {getDisplayName(this.state.usersTyping[1])} {` ${this.props.translate('reportTypingIndicator.areTyping')}`} @@ -88,14 +86,14 @@ class ReportTypingIndicator extends React.Component { default: return ( - - - {this.props.translate('reportTypingIndicator.multipleUsers')} - + {this.props.translate('reportTypingIndicator.multipleUsers')} {` ${this.props.translate('reportTypingIndicator.areTyping')}`} diff --git a/src/styles/styles.js b/src/styles/styles.js index 4d4b6aacd5233..927fe874f2dd7 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -88,14 +88,6 @@ const styles = { lineHeight: 14, }, - textMicroSupportingBold: { - color: themeColors.textSupporting, - fontFamily: fontFamily.GTA_BOLD, - fontWeight: fontWeightBold, - fontSize: variables.fontSizeSmall, - lineHeight: 14, - }, - textLarge: { fontSize: variables.fontSizeLarge, }, From ce291aca71c2039b21fb33c773647233a1820051 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Thu, 29 Jul 2021 03:14:37 +0530 Subject: [PATCH 49/84] fix gap between timerow and composer --- src/styles/styles.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/styles/styles.js b/src/styles/styles.js index 927fe874f2dd7..5fe4a815a07c0 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -967,10 +967,12 @@ const styles = { paddingLeft: 20, paddingRight: 20, display: 'flex', + backgroundColor: themeColors.appBG, }, chatItemComposeWithFirstRow: { minHeight: 90, + marginTop: -16, }, chatItemComposeBoxColor: { From 3fb213ac564083df644aa210bca7616a58abfb89 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Wed, 28 Jul 2021 12:09:14 -1000 Subject: [PATCH 50/84] fix usages --- src/components/LocalePicker.js | 2 +- src/libs/actions/SignInRedirect.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/LocalePicker.js b/src/components/LocalePicker.js index 88a25971d7a3f..b64707cc16ab8 100644 --- a/src/components/LocalePicker.js +++ b/src/components/LocalePicker.js @@ -79,7 +79,7 @@ export default compose( withLocalize, withOnyx({ preferredLocale: { - key: ONYXKEYS.PREFERRED_LOCALE, + key: ONYXKEYS.NVP_PREFERRED_LOCALE, }, betas: { key: ONYXKEYS.BETAS, diff --git a/src/libs/actions/SignInRedirect.js b/src/libs/actions/SignInRedirect.js index a206b5f249ac7..fec0dafc581c8 100644 --- a/src/libs/actions/SignInRedirect.js +++ b/src/libs/actions/SignInRedirect.js @@ -21,7 +21,7 @@ Onyx.connect({ let currentPreferredLocale; Onyx.connect({ - key: ONYXKEYS.PREFERRED_LOCALE, + key: ONYXKEYS.NVP_PREFERRED_LOCALE, callback: val => currentPreferredLocale = val, }); @@ -51,7 +51,7 @@ function redirectToSignIn(errorMessage) { .then(() => { Onyx.clear().then(() => { if (preferredLocale) { - Onyx.set(ONYXKEYS.PREFERRED_LOCALE, preferredLocale); + Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, preferredLocale); } if (errorMessage) { Onyx.set(ONYXKEYS.SESSION, {error: errorMessage}); From bec3d56dd45f6ec066497fc3021251fc17c1ce5e Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Thu, 29 Jul 2021 03:59:07 +0530 Subject: [PATCH 51/84] refactor code and added comments --- src/components/PopoverWithMeasuredContent.js | 9 +++++++++ .../home/report/ContextMenu/ContextMenuActions.js | 2 +- .../ContextMenu/PopoverReportActionContextMenu.js | 15 ++++++++++++--- .../report/ContextMenu/ReportActionContextMenu.js | 2 +- src/pages/home/report/ReportActionsView.js | 7 ++----- 5 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/components/PopoverWithMeasuredContent.js b/src/components/PopoverWithMeasuredContent.js index adb6498d71ffb..e795c42a6788e 100644 --- a/src/components/PopoverWithMeasuredContent.js +++ b/src/components/PopoverWithMeasuredContent.js @@ -64,6 +64,15 @@ class PopoverWithMeasuredContent extends Component { this.measurePopover = this.measurePopover.bind(this); } + /** + * When Popover becomes visible, we need to recalculate the Dimensions. + * Skip render on Popover until recalculations have done by setting isContentMeasured false as early as possible. + * + * @static + * @param {Object} props + * @param {Object} state + * @return {Object|null} + */ static getDerivedStateFromProps(props, state) { // When Popover is shown recalculate if (!state.isVisible && props.isVisible) { diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js index 0cd8e2784744d..1123e54f2623a 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.js +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js @@ -13,7 +13,7 @@ import ReportActionComposeFocusManager from '../../../../libs/ReportActionCompos import {hideContextMenu, showDeleteModal} from './ReportActionContextMenu'; /** - * Gets the markdown version of the message in an action. + * Gets the HTML version of the message in an action. * @param {Object} reportAction * @return {String} */ diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js index 98985bce21490..57bcae4d21301 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js @@ -81,6 +81,12 @@ class PopoverReportActionContextMenu extends React.Component { }); } + /** + * Whether Context Menu is active for the Report Action. + * + * @param {Number|String} actionID + * @return {Boolean} + */ isActiveReportAction(actionID) { return this.state.reportAction.reportActionID === actionID; } @@ -94,8 +100,8 @@ class PopoverReportActionContextMenu extends React.Component { * @param {Number} reportID - Active Report Id * @param {Object} reportAction - ReportAction for ContextMenu * @param {String} draftMessage - ReportAction Draftmessage - * @param {Function} [onShow=() => {}] - Run a callback when Menu is shown - * @param {Function} [onHide=() => {}] - Run a callback when Menu is hidden + * @param {Function} [onShow] - Run a callback when Menu is shown + * @param {Function} [onHide] - Run a callback when Menu is hidden */ showContextMenu( event, @@ -149,7 +155,10 @@ class PopoverReportActionContextMenu extends React.Component { }); } - runAfterContextMenuHide() { + /** + * After Popover hides, call the registered onPopoverHide callback and reset it + */ + runAndResetOnPopoverHide() { this.onPopoverHide(); // After we have called the action, reset it. diff --git a/src/pages/home/report/ContextMenu/ReportActionContextMenu.js b/src/pages/home/report/ContextMenu/ReportActionContextMenu.js index c1b12baa2fe79..7f438d4b28526 100644 --- a/src/pages/home/report/ContextMenu/ReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/ReportActionContextMenu.js @@ -61,7 +61,7 @@ function hideDeleteModal() { if (!contextMenuRef.current) { return; } - return contextMenuRef.current.hideDeleteModal(); + contextMenuRef.current.hideDeleteModal(); } /** diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index 49b8c1e1e9d44..7f5f73b205855 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -221,7 +221,8 @@ class ReportActionsView extends React.Component { /** * Create a unique key for Each Action in the FlatList. - * + * We use a combination of sequenceNumber and clientID in case the clientID are the same - which + * shouldn't happen, but might be possible in some rare cases. * @param {Object} item * @return {String} */ @@ -417,10 +418,6 @@ class ReportActionsView extends React.Component { renderItem={this.renderItem} CellRendererComponent={this.renderCell} contentContainerStyle={styles.chatContentScrollView} - - // We use a combination of sequenceNumber and clientID in case the clientID are the same - which - // shouldn't happen, but might be possible in some rare cases. - // eslint-disable-next-line react/jsx-props-no-multi-spaces keyExtractor={this.keyExtractor} initialRowHeight={32} onEndReached={this.loadMoreChats} From ce3b306d8b9f0e1db1340bdc5e6959d6f5e65acc Mon Sep 17 00:00:00 2001 From: OSBotify Date: Wed, 28 Jul 2021 22:57:52 +0000 Subject: [PATCH 52/84] Update version to 1.0.81-1 --- android/app/build.gradle | 4 ++-- ios/ExpensifyCash/Info.plist | 2 +- ios/ExpensifyCashTests/Info.plist | 2 +- package-lock.json | 2 +- package.json | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index b8c3fb59625fd..dee913e78e97f 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -150,8 +150,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001008100 - versionName "1.0.81-0" + versionCode 1001008101 + versionName "1.0.81-1" } splits { abi { diff --git a/ios/ExpensifyCash/Info.plist b/ios/ExpensifyCash/Info.plist index c83a8899090c9..790b90b03b32d 100644 --- a/ios/ExpensifyCash/Info.plist +++ b/ios/ExpensifyCash/Info.plist @@ -30,7 +30,7 @@ CFBundleVersion - 1.0.81.0 + 1.0.81.1 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/ExpensifyCashTests/Info.plist b/ios/ExpensifyCashTests/Info.plist index 428d035addfc7..135fb4f426e22 100644 --- a/ios/ExpensifyCashTests/Info.plist +++ b/ios/ExpensifyCashTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.0.81.0 + 1.0.81.1 diff --git a/package-lock.json b/package-lock.json index 90dd382d8d1c6..2bff44ebdf79b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "expensify.cash", - "version": "1.0.81-0", + "version": "1.0.81-1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 2c29e02b037c2..d742ee0f86576 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "expensify.cash", - "version": "1.0.81-0", + "version": "1.0.81-1", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "Expensify.cash is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From 0ddf5397936f931a2d9cdaac2d207216651cb2c5 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Wed, 28 Jul 2021 13:40:16 -1000 Subject: [PATCH 53/84] Setup Flipper plugins when on native & dev --- ios/Podfile.lock | 6 ++++++ package-lock.json | 18 ++++++++++++++++++ package.json | 3 +++ src/setup/index.native.js | 10 ++++++++-- 4 files changed, 35 insertions(+), 2 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 3be3a461ff597..766985a36464c 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -412,6 +412,8 @@ PODS: - React-Core - react-native-document-picker (5.1.0): - React-Core + - react-native-flipper (0.100.0): + - React-Core - react-native-image-picker (4.0.3): - React-Core - react-native-netinfo (5.9.10): @@ -632,6 +634,7 @@ DEPENDENCIES: - React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`) - react-native-config (from `../node_modules/react-native-config`) - react-native-document-picker (from `../node_modules/react-native-document-picker`) + - react-native-flipper (from `../node_modules/react-native-flipper`) - react-native-image-picker (from `../node_modules/react-native-image-picker`) - "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)" - react-native-pdf (from `../node_modules/react-native-pdf`) @@ -761,6 +764,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-config" react-native-document-picker: :path: "../node_modules/react-native-document-picker" + react-native-flipper: + :path: "../node_modules/react-native-flipper" react-native-image-picker: :path: "../node_modules/react-native-image-picker" react-native-netinfo: @@ -913,6 +918,7 @@ SPEC CHECKSUMS: React-jsinspector: 500a59626037be5b3b3d89c5151bc3baa9abf1a9 react-native-config: d8b45133fd13d4f23bd2064b72f6e2c08b2763ed react-native-document-picker: 0e3602a4064da040321bafad6848d8b0edcb1d55 + react-native-flipper: 1943b82f2e494c77b741eb1ed257b6734a334b83 react-native-image-picker: 4089335b89b625d4e34d53fb249c48a7a791b3ea react-native-netinfo: 52cf0ee8342548a485e28f4b09e56b477567244d react-native-pdf: 4b5a9e4465a6a3b399e91dc4838eb44ddf716d1f diff --git a/package-lock.json b/package-lock.json index 90dd382d8d1c6..1d62d9157a257 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23837,6 +23837,12 @@ "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", "dev": true }, + "flipper-plugin-bridgespy-client": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/flipper-plugin-bridgespy-client/-/flipper-plugin-bridgespy-client-0.1.9.tgz", + "integrity": "sha512-KdJcKbgnFfkdhQ8+dOLQtpeeQDFkLjKC21fM20iZ1lpZg+XJ5EHRZWhqzwJMgt0pDFLA7Ws2itmZESYtjyyvKA==", + "dev": true + }, "flow-parser": { "version": "0.121.0", "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.121.0.tgz", @@ -35997,6 +36003,12 @@ "resolved": "https://registry.npmjs.org/react-native-document-picker/-/react-native-document-picker-5.1.0.tgz", "integrity": "sha512-XMSDibp1GX0UMlVdsmAgjmf4/FJ+TCvVLWdKjV4QkTIO3TbDKsWSAS1+9jgUYcqIwQpO87SFBkvJ5cjOwx9vNA==" }, + "react-native-flipper": { + "version": "0.100.0", + "resolved": "https://registry.npmjs.org/react-native-flipper/-/react-native-flipper-0.100.0.tgz", + "integrity": "sha512-UsRlT9UAPHs9K+w1gVi/QEZgYem+W1Z2YXwr5NToAwNMsmTCfl0OpfWS0Xw9FiNQIOcpT2MTlPDHzL3sAQH44A==", + "dev": true + }, "react-native-gesture-handler": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-1.9.0.tgz", @@ -37772,6 +37784,12 @@ "inherits": "^2.0.1" } }, + "rn-async-storage-flipper": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/rn-async-storage-flipper/-/rn-async-storage-flipper-0.0.10.tgz", + "integrity": "sha512-Bp8B3oWufAHhc1okewR6EXZL7gRkMQMODYepTpsTgvFq7Iae57Ow4ZIDjumb6BxoCfipFSFBnxvfPhy3PDDBRQ==", + "dev": true + }, "rn-fetch-blob": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/rn-fetch-blob/-/rn-fetch-blob-0.12.0.tgz", diff --git a/package.json b/package.json index 2c29e02b037c2..5c4de0328c5dc 100644 --- a/package.json +++ b/package.json @@ -145,6 +145,7 @@ "eslint-loader": "^4.0.2", "eslint-plugin-detox": "^1.0.0", "eslint-plugin-jest": "^24.1.0", + "flipper-plugin-bridgespy-client": "^0.1.9", "html-webpack-plugin": "^4.3.0", "jest": "^26.5.2", "jest-circus": "^26.5.2", @@ -154,8 +155,10 @@ "portfinder": "^1.0.28", "pusher-js-mock": "^0.3.3", "react-hot-loader": "^4.12.21", + "react-native-flipper": "^0.100.0", "react-native-svg-transformer": "^0.14.3", "react-test-renderer": "16.13.1", + "rn-async-storage-flipper": "0.0.10", "semver": "^7.3.4", "style-loader": "^2.0.0", "wait-port": "^0.2.9", diff --git a/src/setup/index.native.js b/src/setup/index.native.js index 20469e3101ecd..3e6e8990599d5 100644 --- a/src/setup/index.native.js +++ b/src/setup/index.native.js @@ -1,4 +1,10 @@ -// No additional setup required for native platforms +// Setup Flipper plugins when on dev export default function () { - return null; + // eslint-disable-next-line no-undef + if (__DEV__) { + require('flipper-plugin-bridgespy-client'); + const RNAsyncStorageFlipper = require('rn-async-storage-flipper').default; + const AsyncStorage = require('@react-native-async-storage/async-storage').default; + RNAsyncStorageFlipper(AsyncStorage); + } } From 69a06fafae42987b9da306e34d8f60d62f4deded Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 29 Jul 2021 03:14:32 +0300 Subject: [PATCH 54/84] refactor isValidSize --- src/components/AttachmentModal.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index 362da6f393a67..8ea997b0eb12d 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -98,10 +98,7 @@ class AttachmentModal extends PureComponent { * @returns {Boolean} */ isValidSize(file) { - if (!file) { - return true; - } - return parseInt(file.size, 10) < CONST.API_MAX_ATTACHMENT_SIZE; + return !file || parseInt(file.size, 10) < CONST.API_MAX_ATTACHMENT_SIZE; } render() { From 6a629da4ff7dbabcd29603a223ba436534097521 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 29 Jul 2021 03:16:02 +0300 Subject: [PATCH 55/84] use lodashGet --- src/components/AttachmentModal.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index 8ea997b0eb12d..181c718c9f6e2 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -2,6 +2,7 @@ import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; import {View} from 'react-native'; import Str from 'expensify-common/lib/str'; +import lodashGet from 'lodash/get'; import CONST from '../CONST'; import Modal from './Modal'; import AttachmentView from './AttachmentView'; @@ -98,7 +99,7 @@ class AttachmentModal extends PureComponent { * @returns {Boolean} */ isValidSize(file) { - return !file || parseInt(file.size, 10) < CONST.API_MAX_ATTACHMENT_SIZE; + return !file || lodashGet(file, 'size', 0) < CONST.API_MAX_ATTACHMENT_SIZE; } render() { From f071d79d89465a581e8c479aa7e00574f6275e05 Mon Sep 17 00:00:00 2001 From: Matt Allen Date: Wed, 28 Jul 2021 18:20:14 -0700 Subject: [PATCH 56/84] Update Standard.md Starting at line 23, added HTML content to include note "Remove any platforms that aren't affected by this issue" In lines 28-32, removed checkboxes `- [ ] ` and replaced with bullet points so that the check boxes won't generate Tasks for the issues. P/S here https://expensify.slack.com/archives/C01GTK53T8Q/p1627511286424500 --- .github/ISSUE_TEMPLATE/Standard.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/Standard.md b/.github/ISSUE_TEMPLATE/Standard.md index 514d25f8b29f7..f728a9761a362 100644 --- a/.github/ISSUE_TEMPLATE/Standard.md +++ b/.github/ISSUE_TEMPLATE/Standard.md @@ -20,13 +20,16 @@ Describe what actually happened Can the user still use Expensify without this being fixed? Have you informed them of the workaround? ## Platform: + Where is this issue occurring? -- [ ] Web -- [ ] iOS -- [ ] Android -- [ ] Desktop App -- [ ] Mobile Web +- Web +- iOS +- Android +- Desktop App +- Mobile Web **Version Number:** **Logs:** https://stackoverflow.com/c/expensify/questions/4856 From 60e7aa06f7c5bde8d5bf7b3ef5d60e1019483178 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Thu, 29 Jul 2021 08:44:38 +0530 Subject: [PATCH 57/84] chore(bumped-ecommon-ver): Updated expensify-common version --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2bff44ebdf79b..0d5bcfb707f2c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23070,8 +23070,8 @@ } }, "expensify-common": { - "version": "git://github.com/Expensify/expensify-common.git#7d8408c5c78792394eee8e079f115b1380221a23", - "from": "git://github.com/Expensify/expensify-common.git#7d8408c5c78792394eee8e079f115b1380221a23", + "version": "git://github.com/Expensify/expensify-common.git#e83c6998dac6837098745139cb2bdd4e18bf7134", + "from": "git://github.com/Expensify/expensify-common.git#e83c6998dac6837098745139cb2bdd4e18bf7134", "requires": { "classnames": "2.3.1", "clipboard": "2.0.4", diff --git a/package.json b/package.json index d742ee0f86576..3be26083dca88 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "electron-log": "^4.3.5", "electron-serve": "^1.0.0", "electron-updater": "^4.3.4", - "expensify-common": "git://github.com/Expensify/expensify-common.git#7d8408c5c78792394eee8e079f115b1380221a23", + "expensify-common": "git://github.com/Expensify/expensify-common.git#e83c6998dac6837098745139cb2bdd4e18bf7134", "expo-haptics": "^10.0.0", "file-loader": "^6.0.0", "html-entities": "^1.3.1", From 52f48ac4bbc1cbc1f6dd62958d91001f9af979e6 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Thu, 29 Jul 2021 11:14:21 +0530 Subject: [PATCH 58/84] fix minor crash --- .../home/report/ContextMenu/PopoverReportActionContextMenu.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js index 57bcae4d21301..096645e532c41 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js @@ -46,7 +46,7 @@ class PopoverReportActionContextMenu extends React.Component { this.confirmDeleteAndHideModal = this.confirmDeleteAndHideModal.bind(this); this.hideDeleteModal = this.hideDeleteModal.bind(this); this.showDeleteModal = this.showDeleteModal.bind(this); - this.runAfterContextMenuHide = this.runAfterContextMenuHide.bind(this); + this.runAndResetOnPopoverHide = this.runAndResetOnPopoverHide.bind(this); this.getContextMenuMeasuredLocation = this.getContextMenuMeasuredLocation.bind(this); this.isActiveReportAction = this.isActiveReportAction.bind(this); } @@ -226,7 +226,7 @@ class PopoverReportActionContextMenu extends React.Component { Date: Thu, 29 Jul 2021 12:57:15 +0530 Subject: [PATCH 59/84] fix: translation tests --- tests/unit/TranslateTest.js | 62 ++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/tests/unit/TranslateTest.js b/tests/unit/TranslateTest.js index 5f9338f1d893a..3913a5f38bc1d 100644 --- a/tests/unit/TranslateTest.js +++ b/tests/unit/TranslateTest.js @@ -56,36 +56,40 @@ describe('translate', () => { }); describe('Translation Keys', () => { - let activeLanguage; - let path = ''; - function matchKeys(source, target, key) { - path += key ? `${key}.` : ''; - const pathLevel = path; - if (key && !_.has(target, key)) { - console.debug(`🏹 ${path.slice(0, -1)} is missing from ${activeLanguage}.js`); - return; - } - const sourceOBJ = key ? source[key] : source; - const targetOBJ = key ? target[key] : target; - if (_.isObject(sourceOBJ) && !_.isFunction(sourceOBJ)) { - return _.every(_.keys(sourceOBJ), (subKey) => { - path = pathLevel; - return matchKeys(sourceOBJ, targetOBJ, subKey); - }); - } - if (key) { - path = path.slice(0, -(key.length - 1)); - } - return true; + function traverseKeyPath(source, path, keyPaths) { + const pathArray = keyPaths || []; + const keyPath = path ? `${path}.` : ''; + _.each(_.keys(source), (key) => { + if (_.isObject(source[key]) && !_.isFunction(source[key])) { + traverseKeyPath(source[key], keyPath + key, pathArray); + } else { + pathArray.push(keyPath + key); + } + }); + return pathArray; } - it('Does each locale has all the keys', () => { - const excludeLanguages = ['en', 'es-ES']; - const languages = _.without(_.keys(originalTranslations.default), ...excludeLanguages); - const parentLanguage = originalTranslations.default.en; - const hasAllKeys = _.every(languages, (ln) => { - activeLanguage = ln; - return matchKeys(parentLanguage, originalTranslations.default[ln]); + const excludeLanguages = ['en', 'es-ES']; + const languages = _.without(_.keys(originalTranslations.default), ...excludeLanguages); + const parentLanguage = originalTranslations.default.en; + const parentLanguageKeys = traverseKeyPath(parentLanguage); + + _.every(languages, (ln) => { + const languageKeys = traverseKeyPath(originalTranslations.default[ln]); + + it(`Does ${ln} locale has all the keys`, () => { + const hasAllKeys = _.difference(parentLanguageKeys, languageKeys); + if (hasAllKeys.length) { + console.debug(`🏹 [ ${hasAllKeys.join(', ')} ] are missing from ${ln}.js`); + } + expect(hasAllKeys.length).toBe(0); + }); + + it(`Does ${ln} locale has unused keys`, () => { + const hasAllKeys = _.difference(languageKeys, parentLanguageKeys); + if (hasAllKeys.length) { + console.debug(`🏹 [ ${hasAllKeys.join(', ')} ] are unused keys in ${ln}.js`); + } + expect(hasAllKeys.length).toBe(0); }); - expect(hasAllKeys).toBeTruthy(); }); }); From 26626ec2a6f4f2d3eec361df931ef40009d69b85 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Thu, 29 Jul 2021 13:00:35 +0530 Subject: [PATCH 60/84] fix translation files --- src/languages/es.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/languages/es.js b/src/languages/es.js index d5d3f7f35dcd3..4fc61228a13fc 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -178,9 +178,7 @@ export default { profilePage: { profile: 'Perfil', tellUsAboutYourself: '¡Cuéntanos algo sobre tí, nos encantaría conocerte!', - firstName: 'Nombre', john: 'Juan', - lastName: 'Apellidos', doe: 'Nadie', preferredPronouns: 'Pronombres preferidos', selectYourPronouns: 'Selecciona tus pronombres', @@ -429,7 +427,6 @@ export default { 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 ', onFidoFacialScan: 'Onfido’s Facial Scan Policy and Release', - facialScan: 'la política de reconocimiento facial y la exención de Onfido', 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', }, From 555192661257cfb05dcf0a53ee309d82553af2a0 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Thu, 29 Jul 2021 13:08:38 +0530 Subject: [PATCH 61/84] added missing key --- src/languages/es.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/languages/es.js b/src/languages/es.js index 5df4823cd7985..3f6087eb6d3f8 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -4,6 +4,7 @@ export default { cancel: 'Cancelar', yes: 'Si', no: 'No', + ok: 'OK', attachment: 'Archivo Adjunto', to: 'A', optional: 'Opcional', From 158dfb37581d8529889f96c47145e944afc22a8a Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Thu, 29 Jul 2021 18:52:34 +0530 Subject: [PATCH 62/84] refactor tests --- tests/unit/TranslateTest.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/unit/TranslateTest.js b/tests/unit/TranslateTest.js index 3913a5f38bc1d..ba9168b5efbfd 100644 --- a/tests/unit/TranslateTest.js +++ b/tests/unit/TranslateTest.js @@ -70,14 +70,14 @@ describe('Translation Keys', () => { } const excludeLanguages = ['en', 'es-ES']; const languages = _.without(_.keys(originalTranslations.default), ...excludeLanguages); - const parentLanguage = originalTranslations.default.en; - const parentLanguageKeys = traverseKeyPath(parentLanguage); + const mainLanguage = originalTranslations.default.en; + const mainLanguageKeys = traverseKeyPath(mainLanguage); - _.every(languages, (ln) => { + _.each(languages, (ln) => { const languageKeys = traverseKeyPath(originalTranslations.default[ln]); it(`Does ${ln} locale has all the keys`, () => { - const hasAllKeys = _.difference(parentLanguageKeys, languageKeys); + const hasAllKeys = _.difference(mainLanguageKeys, languageKeys); if (hasAllKeys.length) { console.debug(`🏹 [ ${hasAllKeys.join(', ')} ] are missing from ${ln}.js`); } @@ -85,7 +85,7 @@ describe('Translation Keys', () => { }); it(`Does ${ln} locale has unused keys`, () => { - const hasAllKeys = _.difference(languageKeys, parentLanguageKeys); + const hasAllKeys = _.difference(languageKeys, mainLanguageKeys); if (hasAllKeys.length) { console.debug(`🏹 [ ${hasAllKeys.join(', ')} ] are unused keys in ${ln}.js`); } From 0fe9b71539f21de603b0281f0ef5ce18f23dea3a Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Thu, 29 Jul 2021 15:58:03 +0100 Subject: [PATCH 63/84] Add a new copy for company address --- src/languages/en.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/en.js b/src/languages/en.js index 61cfaee21bc4e..ec921ccfcf896 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -44,7 +44,7 @@ export default { dob: 'Date of Birth', ssnLast4: 'Last 4 Digits of SSN', addressNoPO: 'Address (no P.O. boxes)', - companyAddressNoPO: 'Company Address (no P.O. boxes)', + companyAddressNoPO: 'Company Address (PO Boxes and mail drop addresses are NOT allowed)', city: 'City', state: 'State', zip: 'Zip Code', From a2c977c265a996ee9f157cc3c702cbd3be9e96be Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Thu, 29 Jul 2021 05:20:58 -1000 Subject: [PATCH 64/84] Fix broken list components --- src/components/OptionsList.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/components/OptionsList.js b/src/components/OptionsList.js index cfb25b7e5e19a..715edea1c35c5 100644 --- a/src/components/OptionsList.js +++ b/src/components/OptionsList.js @@ -113,6 +113,7 @@ class OptionsList extends Component { this.renderSectionHeader = this.renderSectionHeader.bind(this); this.extractKey = this.extractKey.bind(this); this.onScrollToIndexFailed = this.onScrollToIndexFailed.bind(this); + this.onViewableItemsChanged = this.onViewableItemsChanged.bind(this); this.viewabilityConfig = {viewAreaCoveragePercentThreshold: 95}; this.didLayout = false; } @@ -137,6 +138,15 @@ class OptionsList extends Component { return false; } + onViewableItemsChanged() { + if (this.didLayout || !this.props.onLayout) { + return; + } + + this.didLayout = true; + this.props.onLayout(); + } + /** * We must implement this method in order to use the ref.scrollToLocation() method. * See: https://reactnative.dev/docs/sectionlist#scrolltolocation @@ -238,14 +248,7 @@ class OptionsList extends Component { maxToRenderPerBatch={5} windowSize={5} viewabilityConfig={this.viewabilityConfig} - onViewableItemsChanged={() => { - if (this.didLayout) { - return; - } - - this.didLayout = true; - this.props.onLayout(); - }} + onViewableItemsChanged={this.onViewableItemsChanged} /> ); From d7b2ef0c612d47288299bfd7edb941a65b7dac13 Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Thu, 29 Jul 2021 16:22:47 +0100 Subject: [PATCH 65/84] Add Spanish translation for the comapny address label --- src/languages/es.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/es.js b/src/languages/es.js index df6d010ee03e0..a33252504eece 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -39,7 +39,7 @@ export default { dob: 'Fecha de Nacimiento', ssnLast4: 'Últimos 4 dígitos de su SSN', addressNoPO: 'Dirección (sin Apartado Postal)', - companyAddressNoPO: 'Dirección de la Empresa (sin Apartado Postal)', + companyAddressNoPO: 'Dirección física de la empresa (no se aceptan apartados ni direcciones postales)', city: 'Ciudad', state: 'Estado', zip: 'Código Postal', From 7c07c4702cae865b155d079de62069f55f6fa589 Mon Sep 17 00:00:00 2001 From: OSBotify Date: Thu, 29 Jul 2021 16:07:03 +0000 Subject: [PATCH 66/84] Update version to 1.0.81-2 --- android/app/build.gradle | 4 ++-- ios/ExpensifyCash/Info.plist | 2 +- ios/ExpensifyCashTests/Info.plist | 2 +- package-lock.json | 2 +- package.json | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index dee913e78e97f..dab57ded944ef 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -150,8 +150,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001008101 - versionName "1.0.81-1" + versionCode 1001008102 + versionName "1.0.81-2" } splits { abi { diff --git a/ios/ExpensifyCash/Info.plist b/ios/ExpensifyCash/Info.plist index 790b90b03b32d..ac93657c66237 100644 --- a/ios/ExpensifyCash/Info.plist +++ b/ios/ExpensifyCash/Info.plist @@ -30,7 +30,7 @@ CFBundleVersion - 1.0.81.1 + 1.0.81.2 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/ExpensifyCashTests/Info.plist b/ios/ExpensifyCashTests/Info.plist index 135fb4f426e22..9d2f14a026036 100644 --- a/ios/ExpensifyCashTests/Info.plist +++ b/ios/ExpensifyCashTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.0.81.1 + 1.0.81.2 diff --git a/package-lock.json b/package-lock.json index 865990c79ced7..a6fb5c67629e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "expensify.cash", - "version": "1.0.81-1", + "version": "1.0.81-2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 9af6c170bea3f..314581e2ac111 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "expensify.cash", - "version": "1.0.81-1", + "version": "1.0.81-2", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "Expensify.cash is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From 8167d69333265b1e0fbb1a5bb4e52d5e2069dcc7 Mon Sep 17 00:00:00 2001 From: OSBotify Date: Thu, 29 Jul 2021 16:25:11 +0000 Subject: [PATCH 67/84] Update version to 1.0.81-3 --- android/app/build.gradle | 4 ++-- ios/ExpensifyCash/Info.plist | 2 +- ios/ExpensifyCashTests/Info.plist | 2 +- package-lock.json | 2 +- package.json | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index dab57ded944ef..7468a78780921 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -150,8 +150,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001008102 - versionName "1.0.81-2" + versionCode 1001008103 + versionName "1.0.81-3" } splits { abi { diff --git a/ios/ExpensifyCash/Info.plist b/ios/ExpensifyCash/Info.plist index ac93657c66237..ed8938afed34a 100644 --- a/ios/ExpensifyCash/Info.plist +++ b/ios/ExpensifyCash/Info.plist @@ -30,7 +30,7 @@ CFBundleVersion - 1.0.81.2 + 1.0.81.3 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/ExpensifyCashTests/Info.plist b/ios/ExpensifyCashTests/Info.plist index 9d2f14a026036..ac439ff2064ba 100644 --- a/ios/ExpensifyCashTests/Info.plist +++ b/ios/ExpensifyCashTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.0.81.2 + 1.0.81.3 diff --git a/package-lock.json b/package-lock.json index a6fb5c67629e4..4aca14177681b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "expensify.cash", - "version": "1.0.81-2", + "version": "1.0.81-3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 314581e2ac111..832e07d605621 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "expensify.cash", - "version": "1.0.81-2", + "version": "1.0.81-3", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "Expensify.cash is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From f9baac6621b859ecec0769a858fce0d52e1e40b2 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Thu, 29 Jul 2021 23:44:49 +0530 Subject: [PATCH 68/84] fix: tests --- src/languages/es.js | 8 ++------ tests/unit/TranslateTest.js | 7 +++++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/languages/es.js b/src/languages/es.js index 53948bf239a6d..ac0378dd024f0 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -218,12 +218,6 @@ export default { desktop: { label: 'Desktop', }, - window: { - label: 'Desktop', - }, - Rajat: { - label: 'Desktop', - }, }, signOut: 'Desconectar', versionLetter: 'v', @@ -261,6 +255,7 @@ export default { addFirstPaymentMethod: 'Añade un método de pago para enviar y recibir pagos directamente desde la aplicación', }, preferencesPage: { + mostRecent: 'Más Recientes', mostRecentModeDescription: 'Esta opción muestra por defecto todos los chats, ordenados a partir del más reciente, con los chats destacados arriba de todo', focus: '#concentración', focusModeDescription: '#concentración – Muestra sólo los chats no leídos y destacados ordenados alfabéticamente.', @@ -302,6 +297,7 @@ export default { twoFactorCode: 'Autenticación de 2 factores', requiredWhen2FAEnabled: 'Obligatorio cuando A2F está habilitado', error: { + incorrectLoginOrPassword: 'Usuario o clave incorrectos. Por favor inténtalo de nuevo', twoFactorAuthenticationEnabled: 'Tienes autenticación de 2 factores activada en esta cuenta. Por favor conéctate usando su email o número de teléfono', invalidLoginOrPassword: 'Usuario o clave incorrectos. Por favor inténtalo de nuevo o resetea tu clave', unableToResetPassword: 'No pudimos cambiar tu clave. Probablemente porque el enlace para resetear la clave ha expirado. Te hemos enviado un nuevo enlace. Chequea tu bandeja de entrada y tu carpeta de Spam', diff --git a/tests/unit/TranslateTest.js b/tests/unit/TranslateTest.js index ba9168b5efbfd..5d85bcd3f333c 100644 --- a/tests/unit/TranslateTest.js +++ b/tests/unit/TranslateTest.js @@ -1,4 +1,5 @@ const _ = require('underscore'); +const {error: AnnotationError} = require('@actions/core'); const translate = require('../../src/libs/translate'); const CONFIG = require('../../src/CONFIG'); const translations = require('../../src/languages/translations'); @@ -80,16 +81,18 @@ describe('Translation Keys', () => { const hasAllKeys = _.difference(mainLanguageKeys, languageKeys); if (hasAllKeys.length) { console.debug(`🏹 [ ${hasAllKeys.join(', ')} ] are missing from ${ln}.js`); + AnnotationError(`🏹 [ ${hasAllKeys.join(', ')} ] are missing from ${ln}.js`); } - expect(hasAllKeys.length).toBe(0); + expect(hasAllKeys).toEqual([]); }); it(`Does ${ln} locale has unused keys`, () => { const hasAllKeys = _.difference(languageKeys, mainLanguageKeys); if (hasAllKeys.length) { console.debug(`🏹 [ ${hasAllKeys.join(', ')} ] are unused keys in ${ln}.js`); + AnnotationError(`🏹 [ ${hasAllKeys.join(', ')} ] are unused keys in ${ln}.js`); } - expect(hasAllKeys.length).toBe(0); + expect(hasAllKeys).toEqual([]); }); }); }); From a85cf39e8dc4a28c11a41b4ed3172a023e910ae8 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 29 Jul 2021 22:27:15 +0300 Subject: [PATCH 69/84] reduce debounce time to 75ms --- src/pages/SearchPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/SearchPage.js b/src/pages/SearchPage.js index 7244c9460a34c..03b6679deab41 100755 --- a/src/pages/SearchPage.js +++ b/src/pages/SearchPage.js @@ -66,7 +66,7 @@ class SearchPage extends Component { this.selectReport = this.selectReport.bind(this); this.onChangeText = this.onChangeText.bind(this); - this.debouncedUpdateOptions = _.debounce(this.updateOptions.bind(this), 300); + this.debouncedUpdateOptions = _.debounce(this.updateOptions.bind(this), 75); const { recentReports, From 241eb8446dc49297451eec54a3c6a1427c22f2e0 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Thu, 29 Jul 2021 09:45:23 -1000 Subject: [PATCH 70/84] update Onyx version --- package-lock.json | 27 +++++++++++---------------- package.json | 2 +- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2c2d462c6be92..3aa3f134824e1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16350,6 +16350,11 @@ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" }, + "ascii-table": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/ascii-table/-/ascii-table-0.0.9.tgz", + "integrity": "sha1-BqZgTWpV1L9BqaR9mHLXp42jHnM=" + }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -36075,12 +36080,12 @@ } }, "react-native-onyx": { - "version": "git+https://github.com/Expensify/react-native-onyx.git#84a27cdd03a39baa167058efc3379d9a477849dd", - "from": "git+https://github.com/Expensify/react-native-onyx.git#84a27cdd03a39baa167058efc3379d9a477849dd", + "version": "git+https://github.com/Expensify/react-native-onyx.git#61e688873dd582af22d6461c416d9967304e67a8", + "from": "git+https://github.com/Expensify/react-native-onyx.git#61e688873dd582af22d6461c416d9967304e67a8", "requires": { + "ascii-table": "0.0.9", "expensify-common": "git+https://github.com/Expensify/expensify-common.git#2e5cff552cf132da90a3fb9756e6b4fb6ae7b40c", "lodash": "4.17.21", - "react": "^16.13.1", "underscore": "^1.13.1" }, "dependencies": { @@ -36101,16 +36106,6 @@ "underscore": "1.9.1" }, "dependencies": { - "react": { - "version": "16.12.0", - "resolved": "https://registry.npmjs.org/react/-/react-16.12.0.tgz", - "integrity": "sha512-fglqy3k5E+81pA8s+7K0/T3DBCF0ZDOher1elBFzF7O6arXJgzyu/FW+COxFvAWXJoJN9KIZbT2LXlukwphYTA==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2" - } - }, "underscore": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", @@ -36119,9 +36114,9 @@ } }, "react": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", - "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", + "version": "16.12.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.12.0.tgz", + "integrity": "sha512-fglqy3k5E+81pA8s+7K0/T3DBCF0ZDOher1elBFzF7O6arXJgzyu/FW+COxFvAWXJoJN9KIZbT2LXlukwphYTA==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", diff --git a/package.json b/package.json index 947c939481f39..6adb1cd45e27f 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "react-native-image-picker": "^4.0.3", "react-native-keyboard-spacer": "^0.4.1", "react-native-modal": "^11.10.0", - "react-native-onyx": "git+https://github.com/Expensify/react-native-onyx.git#84a27cdd03a39baa167058efc3379d9a477849dd", + "react-native-onyx": "git+https://github.com/Expensify/react-native-onyx.git#61e688873dd582af22d6461c416d9967304e67a8", "react-native-pdf": "^6.2.2", "react-native-permissions": "^3.0.1", "react-native-picker-select": "8.0.4", From 92bccde3a648d0e60a382607bdd573d7a46eb6ab Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Thu, 29 Jul 2021 10:33:20 -1000 Subject: [PATCH 71/84] Update test so initial values are correct --- tests/unit/NetworkTest.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/unit/NetworkTest.js b/tests/unit/NetworkTest.js index b168a82df0fc7..ac0e411bd8b7f 100644 --- a/tests/unit/NetworkTest.js +++ b/tests/unit/NetworkTest.js @@ -15,6 +15,13 @@ jest.mock('../../src/libs/Notification/PushNotification', () => ({ jest.useFakeTimers(); +Onyx.init({ + keys: ONYXKEYS, + registerStorageEventListener: () => {}, +}); + +beforeEach(() => Onyx.clear().then(waitForPromisesToResolve)); + test('failing to reauthenticate while offline should not log out user', () => { // Given a test user login and account ID const TEST_USER_LOGIN = 'test@testguy.com'; From 94b004b6af3616a55e290240f03f5270b08b1a52 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Thu, 29 Jul 2021 10:53:30 -1000 Subject: [PATCH 72/84] update again --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3aa3f134824e1..7f384ef69143b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36080,8 +36080,8 @@ } }, "react-native-onyx": { - "version": "git+https://github.com/Expensify/react-native-onyx.git#61e688873dd582af22d6461c416d9967304e67a8", - "from": "git+https://github.com/Expensify/react-native-onyx.git#61e688873dd582af22d6461c416d9967304e67a8", + "version": "git+https://github.com/Expensify/react-native-onyx.git#d73900b7cb7bf82bed77cb6b6baabf8fe2eb3a0e", + "from": "git+https://github.com/Expensify/react-native-onyx.git#d73900b7cb7bf82bed77cb6b6baabf8fe2eb3a0e", "requires": { "ascii-table": "0.0.9", "expensify-common": "git+https://github.com/Expensify/expensify-common.git#2e5cff552cf132da90a3fb9756e6b4fb6ae7b40c", diff --git a/package.json b/package.json index 6adb1cd45e27f..311399383b39c 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "react-native-image-picker": "^4.0.3", "react-native-keyboard-spacer": "^0.4.1", "react-native-modal": "^11.10.0", - "react-native-onyx": "git+https://github.com/Expensify/react-native-onyx.git#61e688873dd582af22d6461c416d9967304e67a8", + "react-native-onyx": "git+https://github.com/Expensify/react-native-onyx.git#d73900b7cb7bf82bed77cb6b6baabf8fe2eb3a0e", "react-native-pdf": "^6.2.2", "react-native-permissions": "^3.0.1", "react-native-picker-select": "8.0.4", From fcc8745e95358374d97b9c93c168666eccf31ec1 Mon Sep 17 00:00:00 2001 From: joelbettner Date: Thu, 29 Jul 2021 15:01:57 -0600 Subject: [PATCH 73/84] Different throw for Missing partnerUserSecret --- src/libs/API.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libs/API.js b/src/libs/API.js index c885298411b0e..5a0c4f2cbec10 100644 --- a/src/libs/API.js +++ b/src/libs/API.js @@ -221,6 +221,11 @@ function Authenticate(parameters) { case 401: throw new Error('passwordForm.error.incorrectLoginOrPassword'); case 402: + // If too few characters are passed as the password, the WAF will pass it to the API as an empty + // string, which results in a 402 error from Auth. + if (response.message === "402 Missing partnerUserSecret") { + throw new Error('passwordForm.error.incorrectLoginOrPassword'); + } throw new Error('passwordForm.error.twoFactorAuthenticationEnabled'); case 403: throw new Error('passwordForm.error.invalidLoginOrPassword'); From d363e6c01cc17699af080915a120f9ce55addbb9 Mon Sep 17 00:00:00 2001 From: joelbettner Date: Thu, 29 Jul 2021 15:26:29 -0600 Subject: [PATCH 74/84] Using single quotes --- src/libs/API.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/API.js b/src/libs/API.js index 5a0c4f2cbec10..5fa63cf9dbf4f 100644 --- a/src/libs/API.js +++ b/src/libs/API.js @@ -223,7 +223,7 @@ function Authenticate(parameters) { case 402: // If too few characters are passed as the password, the WAF will pass it to the API as an empty // string, which results in a 402 error from Auth. - if (response.message === "402 Missing partnerUserSecret") { + if (response.message === '402 Missing partnerUserSecret') { throw new Error('passwordForm.error.incorrectLoginOrPassword'); } throw new Error('passwordForm.error.twoFactorAuthenticationEnabled'); From d21e44b3f51a447f1f778ee2adad3dd998a6f257 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Thu, 29 Jul 2021 14:00:15 -1000 Subject: [PATCH 75/84] derp --- .../reactnativechat/ReactNativeFlipper.java | 2 +- .../com/expensify/chat/MainApplication.java | 3 +- android/gradle.properties | 2 +- package-lock.json | 474 +++++++++--------- src/pages/home/ReportScreen.js | 4 +- src/pages/home/report/ReportActionsView.js | 1 + 6 files changed, 243 insertions(+), 243 deletions(-) diff --git a/android/app/src/debug/java/com/reactnativechat/ReactNativeFlipper.java b/android/app/src/debug/java/com/reactnativechat/ReactNativeFlipper.java index 2bbba01ba11d9..ae2d14ce9b67c 100644 --- a/android/app/src/debug/java/com/reactnativechat/ReactNativeFlipper.java +++ b/android/app/src/debug/java/com/reactnativechat/ReactNativeFlipper.java @@ -4,7 +4,7 @@ *

This source code is licensed under the MIT license found in the LICENSE file in the root * directory of this source tree. */ -package com.expensifyreactnative.chat; +package com.reactnativechat; import android.content.Context; import com.facebook.flipper.android.AndroidFlipperClient; diff --git a/android/app/src/main/java/com/expensify/chat/MainApplication.java b/android/app/src/main/java/com/expensify/chat/MainApplication.java index 0a01f65adc6ca..1caabc78617dc 100644 --- a/android/app/src/main/java/com/expensify/chat/MainApplication.java +++ b/android/app/src/main/java/com/expensify/chat/MainApplication.java @@ -20,7 +20,6 @@ import org.unimodules.adapters.react.ModuleRegistryAdapter; import org.unimodules.adapters.react.ReactModuleRegistryProvider; -import org.unimodules.core.interfaces.SingletonModule; import com.facebook.react.bridge.JSIModulePackage; import com.swmansion.reanimated.ReanimatedJSIModulePackage; @@ -107,7 +106,7 @@ private static void initializeFlipper( We use reflection here to pick up the class that initializes Flipper, since Flipper library is not available in release mode */ - Class aClass = Class.forName("com.expensify.chat.ReactNativeFlipper"); + Class aClass = Class.forName("com.reactnativechat.ReactNativeFlipper"); aClass .getMethod("initializeFlipper", Context.class, ReactInstanceManager.class) .invoke(null, context, reactInstanceManager); diff --git a/android/gradle.properties b/android/gradle.properties index 75c02e4558b36..87a5a217a8a43 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -29,7 +29,7 @@ AsyncStorage_db_size_in_MB=10 AsyncStorage_useNextStorage=true # Version of flipper SDK to use with React Native -FLIPPER_VERSION=0.54.0 +FLIPPER_VERSION=0.100.0 # Key Store Information MYAPP_UPLOAD_STORE_FILE=my-upload-key.keystore diff --git a/package-lock.json b/package-lock.json index 7f384ef69143b..110742c8d0944 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2624,9 +2624,9 @@ }, "dependencies": { "@babel/compat-data": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.5.tgz", - "integrity": "sha512-kixrYn4JwfAVPa0f2yfzc2AWti6WRRyO3XjWW5PJAvtE11qhSayrrcrEnee05KAtNaPC+EwehE8Qt1UedEVB8w==" + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.7.tgz", + "integrity": "sha512-nS6dZaISCXJ3+518CWiBfEr//gHyMO02uDxBkXTKZDN5POruCnOZ1N4YBRZDCabwF8nZMWBpRxIicmXtBs+fvw==" }, "@babel/core": { "version": "7.9.0", @@ -2667,11 +2667,11 @@ }, "dependencies": { "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", + "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "to-fast-properties": "^2.0.0" } } @@ -2687,11 +2687,11 @@ }, "dependencies": { "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", + "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "to-fast-properties": "^2.0.0" } } @@ -2716,13 +2716,13 @@ } }, "@babel/helper-create-class-features-plugin": { - "version": "7.14.6", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.14.6.tgz", - "integrity": "sha512-Z6gsfGofTxH/+LQXqYEK45kxmcensbzmk/oi8DmaQytlQCgqNZt9XQF8iqlI/SeXWVjaMNxvYvzaYw+kh42mDg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.14.8.tgz", + "integrity": "sha512-bpYvH8zJBWzeqi1o+co8qOrw+EXzQ/0c74gVmY205AWXy9nifHrOg77y+1zwxX5lXE7Icq4sPlSQ4O2kWBrteQ==", "requires": { "@babel/helper-annotate-as-pure": "^7.14.5", "@babel/helper-function-name": "^7.14.5", - "@babel/helper-member-expression-to-functions": "^7.14.5", + "@babel/helper-member-expression-to-functions": "^7.14.7", "@babel/helper-optimise-call-expression": "^7.14.5", "@babel/helper-replace-supers": "^7.14.5", "@babel/helper-split-export-declaration": "^7.14.5" @@ -2746,11 +2746,11 @@ }, "dependencies": { "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", + "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "to-fast-properties": "^2.0.0" } } @@ -2775,9 +2775,9 @@ } }, "@babel/parser": { - "version": "7.14.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.6.tgz", - "integrity": "sha512-oG0ej7efjEXxb4UgE+klVx+3j4MVo+A2vCzm7OUN4CLo6WhQ+vSOD2yJ8m7B+DghObxtLxt3EfgMWpq+AsWehQ==" + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.8.tgz", + "integrity": "sha512-syoCQFOoo/fzkWDeM0dLEZi5xqurb5vuyzwIMNZRNun+N/9A4cUZeQaE7dTrB8jGaKuJRBtEOajtnmw0I5hvvA==" }, "@babel/template": { "version": "7.14.5", @@ -2790,11 +2790,11 @@ } }, "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", + "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "to-fast-properties": "^2.0.0" } } @@ -2809,11 +2809,11 @@ }, "dependencies": { "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", + "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "to-fast-properties": "^2.0.0" } } @@ -2828,30 +2828,30 @@ }, "dependencies": { "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", + "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "to-fast-properties": "^2.0.0" } } } }, "@babel/helper-member-expression-to-functions": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.14.5.tgz", - "integrity": "sha512-UxUeEYPrqH1Q/k0yRku1JE7dyfyehNwT6SVkMHvYvPDv4+uu627VXBckVj891BO8ruKBkiDoGnZf4qPDD8abDQ==", + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.14.7.tgz", + "integrity": "sha512-TMUt4xKxJn6ccjcOW7c4hlwyJArizskAhoSTOCkA0uZ+KghIaci0Qg9R043kUMWI9mtQfgny+NQ5QATnZ+paaA==", "requires": { "@babel/types": "^7.14.5" }, "dependencies": { "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", + "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "to-fast-properties": "^2.0.0" } } @@ -2866,11 +2866,11 @@ }, "dependencies": { "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", + "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "to-fast-properties": "^2.0.0" } } @@ -2885,11 +2885,11 @@ }, "dependencies": { "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", + "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "to-fast-properties": "^2.0.0" } } @@ -2911,11 +2911,11 @@ }, "dependencies": { "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", + "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "to-fast-properties": "^2.0.0" } } @@ -2941,61 +2941,61 @@ } }, "@babel/generator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz", - "integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.8.tgz", + "integrity": "sha512-cYDUpvIzhBVnMzRoY1fkSEhK/HmwEVwlyULYgn/tMQYd6Obag3ylCjONle3gdErfXBW61SVTlR9QR7uWlgeIkg==", "requires": { - "@babel/types": "^7.14.5", + "@babel/types": "^7.14.8", "jsesc": "^2.5.1", "source-map": "^0.5.0" } }, "@babel/parser": { - "version": "7.14.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.6.tgz", - "integrity": "sha512-oG0ej7efjEXxb4UgE+klVx+3j4MVo+A2vCzm7OUN4CLo6WhQ+vSOD2yJ8m7B+DghObxtLxt3EfgMWpq+AsWehQ==" + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.8.tgz", + "integrity": "sha512-syoCQFOoo/fzkWDeM0dLEZi5xqurb5vuyzwIMNZRNun+N/9A4cUZeQaE7dTrB8jGaKuJRBtEOajtnmw0I5hvvA==" }, "@babel/traverse": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.5.tgz", - "integrity": "sha512-G3BiS15vevepdmFqmUc9X+64y0viZYygubAMO8SvBmKARuF6CPSZtH4Ng9vi/lrWlZFGe3FWdXNy835akH8Glg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.8.tgz", + "integrity": "sha512-kexHhzCljJcFNn1KYAQ6A5wxMRzq9ebYpEDV4+WdNyr3i7O44tanbDOR/xjiG2F3sllan+LgwK+7OMk0EmydHg==", "requires": { "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.14.5", + "@babel/generator": "^7.14.8", "@babel/helper-function-name": "^7.14.5", "@babel/helper-hoist-variables": "^7.14.5", "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/parser": "^7.14.5", - "@babel/types": "^7.14.5", + "@babel/parser": "^7.14.8", + "@babel/types": "^7.14.8", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", + "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "to-fast-properties": "^2.0.0" } } } }, "@babel/helper-simple-access": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.14.5.tgz", - "integrity": "sha512-nfBN9xvmCt6nrMZjfhkl7i0oTV3yxR4/FztsbOASyTvVcoYd0TRHh7eMLdlEcCqobydC0LAF3LtC92Iwxo0wyw==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.14.8.tgz", + "integrity": "sha512-TrFN4RHh9gnWEU+s7JloIho2T76GPwRHhdzOWLqTrMnlas8T9O7ec+oEDNsRXndOmru9ymH9DFrEOxpzPoSbdg==", "requires": { - "@babel/types": "^7.14.5" + "@babel/types": "^7.14.8" }, "dependencies": { "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", + "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "to-fast-properties": "^2.0.0" } } @@ -3010,11 +3010,11 @@ }, "dependencies": { "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", + "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "to-fast-properties": "^2.0.0" } } @@ -3029,20 +3029,20 @@ }, "dependencies": { "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", + "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "to-fast-properties": "^2.0.0" } } } }, "@babel/helper-validator-identifier": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", - "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==" + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.8.tgz", + "integrity": "sha512-ZGy6/XQjllhYQrNw/3zfWRwZCTVSiBLZ9DHVZxn9n2gip/7ab8mv2TWlKPIBk26RwedCBoWdjLmn+t9na2Gcow==" }, "@babel/helper-validator-option": { "version": "7.14.5", @@ -3069,19 +3069,19 @@ } }, "@babel/generator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz", - "integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.8.tgz", + "integrity": "sha512-cYDUpvIzhBVnMzRoY1fkSEhK/HmwEVwlyULYgn/tMQYd6Obag3ylCjONle3gdErfXBW61SVTlR9QR7uWlgeIkg==", "requires": { - "@babel/types": "^7.14.5", + "@babel/types": "^7.14.8", "jsesc": "^2.5.1", "source-map": "^0.5.0" } }, "@babel/parser": { - "version": "7.14.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.6.tgz", - "integrity": "sha512-oG0ej7efjEXxb4UgE+klVx+3j4MVo+A2vCzm7OUN4CLo6WhQ+vSOD2yJ8m7B+DghObxtLxt3EfgMWpq+AsWehQ==" + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.8.tgz", + "integrity": "sha512-syoCQFOoo/fzkWDeM0dLEZi5xqurb5vuyzwIMNZRNun+N/9A4cUZeQaE7dTrB8jGaKuJRBtEOajtnmw0I5hvvA==" }, "@babel/template": { "version": "7.14.5", @@ -3094,27 +3094,27 @@ } }, "@babel/traverse": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.5.tgz", - "integrity": "sha512-G3BiS15vevepdmFqmUc9X+64y0viZYygubAMO8SvBmKARuF6CPSZtH4Ng9vi/lrWlZFGe3FWdXNy835akH8Glg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.8.tgz", + "integrity": "sha512-kexHhzCljJcFNn1KYAQ6A5wxMRzq9ebYpEDV4+WdNyr3i7O44tanbDOR/xjiG2F3sllan+LgwK+7OMk0EmydHg==", "requires": { "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.14.5", + "@babel/generator": "^7.14.8", "@babel/helper-function-name": "^7.14.5", "@babel/helper-hoist-variables": "^7.14.5", "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/parser": "^7.14.5", - "@babel/types": "^7.14.5", + "@babel/parser": "^7.14.8", + "@babel/types": "^7.14.8", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", + "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "to-fast-properties": "^2.0.0" } } @@ -3131,9 +3131,9 @@ } }, "@babel/plugin-proposal-async-generator-functions": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.14.5.tgz", - "integrity": "sha512-tbD/CG3l43FIXxmu4a7RBe4zH7MLJ+S/lFowPFO7HetS2hyOZ/0nnnznegDuzFzfkyQYTxqdTH/hKmuBngaDAA==", + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.14.7.tgz", + "integrity": "sha512-RK8Wj7lXLY3bqei69/cc25gwS5puEc3dknoFPFbqfy3XxYQBQFvu4ioWpafMBAB+L9NyptQK4nMOa5Xz16og8Q==", "requires": { "@babel/helper-plugin-utils": "^7.14.5", "@babel/helper-remap-async-to-generator": "^7.14.5", @@ -3204,11 +3204,11 @@ } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.14.5.tgz", - "integrity": "sha512-VzMyY6PWNPPT3pxc5hi9LloKNr4SSrVCg7Yr6aZpW4Ym07r7KqSU/QXYwjXLVxqwSv0t/XSXkFoKBPUkZ8vb2A==", + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.14.7.tgz", + "integrity": "sha512-082hsZz+sVabfmDWo1Oct1u1AgbKbUAyVgmX4otIc7bdsRgHBXwTwb3DpDmD4Eyyx6DNiuz5UAATT655k+kL5g==", "requires": { - "@babel/compat-data": "^7.14.5", + "@babel/compat-data": "^7.14.7", "@babel/helper-compilation-targets": "^7.14.5", "@babel/helper-plugin-utils": "^7.14.5", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", @@ -3333,9 +3333,9 @@ } }, "@babel/plugin-transform-destructuring": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.14.5.tgz", - "integrity": "sha512-wU9tYisEbRMxqDezKUqC9GleLycCRoUsai9ddlsq54r8QRLaeEhc+d+9DqCG+kV9W2GgQjTZESPTpn5bAFMDww==", + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.14.7.tgz", + "integrity": "sha512-0mDE99nK+kVh3xlc5vKwB6wnP9ecuSj+zQCa/n0voENtP/zymdT4HH6QEb65wjjcbqr1Jb/7z9Qp7TF5FtwYGw==", "requires": { "@babel/helper-plugin-utils": "^7.14.5" } @@ -3418,34 +3418,34 @@ } }, "@babel/generator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz", - "integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.8.tgz", + "integrity": "sha512-cYDUpvIzhBVnMzRoY1fkSEhK/HmwEVwlyULYgn/tMQYd6Obag3ylCjONle3gdErfXBW61SVTlR9QR7uWlgeIkg==", "requires": { - "@babel/types": "^7.14.5", + "@babel/types": "^7.14.8", "jsesc": "^2.5.1", "source-map": "^0.5.0" } }, "@babel/helper-module-transforms": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.5.tgz", - "integrity": "sha512-iXpX4KW8LVODuAieD7MzhNjmM6dzYY5tfRqT+R9HDXWl0jPn/djKmA+G9s/2C2T9zggw5tK1QNqZ70USfedOwA==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.8.tgz", + "integrity": "sha512-RyE+NFOjXn5A9YU1dkpeBaduagTlZ0+fccnIcAGbv1KGUlReBj7utF7oEth8IdIBQPcux0DDgW5MFBH2xu9KcA==", "requires": { "@babel/helper-module-imports": "^7.14.5", "@babel/helper-replace-supers": "^7.14.5", - "@babel/helper-simple-access": "^7.14.5", + "@babel/helper-simple-access": "^7.14.8", "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "@babel/template": "^7.14.5", - "@babel/traverse": "^7.14.5", - "@babel/types": "^7.14.5" + "@babel/traverse": "^7.14.8", + "@babel/types": "^7.14.8" } }, "@babel/parser": { - "version": "7.14.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.6.tgz", - "integrity": "sha512-oG0ej7efjEXxb4UgE+klVx+3j4MVo+A2vCzm7OUN4CLo6WhQ+vSOD2yJ8m7B+DghObxtLxt3EfgMWpq+AsWehQ==" + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.8.tgz", + "integrity": "sha512-syoCQFOoo/fzkWDeM0dLEZi5xqurb5vuyzwIMNZRNun+N/9A4cUZeQaE7dTrB8jGaKuJRBtEOajtnmw0I5hvvA==" }, "@babel/template": { "version": "7.14.5", @@ -3458,27 +3458,27 @@ } }, "@babel/traverse": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.5.tgz", - "integrity": "sha512-G3BiS15vevepdmFqmUc9X+64y0viZYygubAMO8SvBmKARuF6CPSZtH4Ng9vi/lrWlZFGe3FWdXNy835akH8Glg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.8.tgz", + "integrity": "sha512-kexHhzCljJcFNn1KYAQ6A5wxMRzq9ebYpEDV4+WdNyr3i7O44tanbDOR/xjiG2F3sllan+LgwK+7OMk0EmydHg==", "requires": { "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.14.5", + "@babel/generator": "^7.14.8", "@babel/helper-function-name": "^7.14.5", "@babel/helper-hoist-variables": "^7.14.5", "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/parser": "^7.14.5", - "@babel/types": "^7.14.5", + "@babel/parser": "^7.14.8", + "@babel/types": "^7.14.8", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", + "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "to-fast-properties": "^2.0.0" } } @@ -3504,34 +3504,34 @@ } }, "@babel/generator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz", - "integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.8.tgz", + "integrity": "sha512-cYDUpvIzhBVnMzRoY1fkSEhK/HmwEVwlyULYgn/tMQYd6Obag3ylCjONle3gdErfXBW61SVTlR9QR7uWlgeIkg==", "requires": { - "@babel/types": "^7.14.5", + "@babel/types": "^7.14.8", "jsesc": "^2.5.1", "source-map": "^0.5.0" } }, "@babel/helper-module-transforms": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.5.tgz", - "integrity": "sha512-iXpX4KW8LVODuAieD7MzhNjmM6dzYY5tfRqT+R9HDXWl0jPn/djKmA+G9s/2C2T9zggw5tK1QNqZ70USfedOwA==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.8.tgz", + "integrity": "sha512-RyE+NFOjXn5A9YU1dkpeBaduagTlZ0+fccnIcAGbv1KGUlReBj7utF7oEth8IdIBQPcux0DDgW5MFBH2xu9KcA==", "requires": { "@babel/helper-module-imports": "^7.14.5", "@babel/helper-replace-supers": "^7.14.5", - "@babel/helper-simple-access": "^7.14.5", + "@babel/helper-simple-access": "^7.14.8", "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "@babel/template": "^7.14.5", - "@babel/traverse": "^7.14.5", - "@babel/types": "^7.14.5" + "@babel/traverse": "^7.14.8", + "@babel/types": "^7.14.8" } }, "@babel/parser": { - "version": "7.14.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.6.tgz", - "integrity": "sha512-oG0ej7efjEXxb4UgE+klVx+3j4MVo+A2vCzm7OUN4CLo6WhQ+vSOD2yJ8m7B+DghObxtLxt3EfgMWpq+AsWehQ==" + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.8.tgz", + "integrity": "sha512-syoCQFOoo/fzkWDeM0dLEZi5xqurb5vuyzwIMNZRNun+N/9A4cUZeQaE7dTrB8jGaKuJRBtEOajtnmw0I5hvvA==" }, "@babel/template": { "version": "7.14.5", @@ -3544,27 +3544,27 @@ } }, "@babel/traverse": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.5.tgz", - "integrity": "sha512-G3BiS15vevepdmFqmUc9X+64y0viZYygubAMO8SvBmKARuF6CPSZtH4Ng9vi/lrWlZFGe3FWdXNy835akH8Glg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.8.tgz", + "integrity": "sha512-kexHhzCljJcFNn1KYAQ6A5wxMRzq9ebYpEDV4+WdNyr3i7O44tanbDOR/xjiG2F3sllan+LgwK+7OMk0EmydHg==", "requires": { "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.14.5", + "@babel/generator": "^7.14.8", "@babel/helper-function-name": "^7.14.5", "@babel/helper-hoist-variables": "^7.14.5", "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/parser": "^7.14.5", - "@babel/types": "^7.14.5", + "@babel/parser": "^7.14.8", + "@babel/types": "^7.14.8", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", + "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "to-fast-properties": "^2.0.0" } } @@ -3591,34 +3591,34 @@ } }, "@babel/generator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz", - "integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.8.tgz", + "integrity": "sha512-cYDUpvIzhBVnMzRoY1fkSEhK/HmwEVwlyULYgn/tMQYd6Obag3ylCjONle3gdErfXBW61SVTlR9QR7uWlgeIkg==", "requires": { - "@babel/types": "^7.14.5", + "@babel/types": "^7.14.8", "jsesc": "^2.5.1", "source-map": "^0.5.0" } }, "@babel/helper-module-transforms": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.5.tgz", - "integrity": "sha512-iXpX4KW8LVODuAieD7MzhNjmM6dzYY5tfRqT+R9HDXWl0jPn/djKmA+G9s/2C2T9zggw5tK1QNqZ70USfedOwA==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.8.tgz", + "integrity": "sha512-RyE+NFOjXn5A9YU1dkpeBaduagTlZ0+fccnIcAGbv1KGUlReBj7utF7oEth8IdIBQPcux0DDgW5MFBH2xu9KcA==", "requires": { "@babel/helper-module-imports": "^7.14.5", "@babel/helper-replace-supers": "^7.14.5", - "@babel/helper-simple-access": "^7.14.5", + "@babel/helper-simple-access": "^7.14.8", "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "@babel/template": "^7.14.5", - "@babel/traverse": "^7.14.5", - "@babel/types": "^7.14.5" + "@babel/traverse": "^7.14.8", + "@babel/types": "^7.14.8" } }, "@babel/parser": { - "version": "7.14.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.6.tgz", - "integrity": "sha512-oG0ej7efjEXxb4UgE+klVx+3j4MVo+A2vCzm7OUN4CLo6WhQ+vSOD2yJ8m7B+DghObxtLxt3EfgMWpq+AsWehQ==" + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.8.tgz", + "integrity": "sha512-syoCQFOoo/fzkWDeM0dLEZi5xqurb5vuyzwIMNZRNun+N/9A4cUZeQaE7dTrB8jGaKuJRBtEOajtnmw0I5hvvA==" }, "@babel/template": { "version": "7.14.5", @@ -3631,27 +3631,27 @@ } }, "@babel/traverse": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.5.tgz", - "integrity": "sha512-G3BiS15vevepdmFqmUc9X+64y0viZYygubAMO8SvBmKARuF6CPSZtH4Ng9vi/lrWlZFGe3FWdXNy835akH8Glg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.8.tgz", + "integrity": "sha512-kexHhzCljJcFNn1KYAQ6A5wxMRzq9ebYpEDV4+WdNyr3i7O44tanbDOR/xjiG2F3sllan+LgwK+7OMk0EmydHg==", "requires": { "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.14.5", + "@babel/generator": "^7.14.8", "@babel/helper-function-name": "^7.14.5", "@babel/helper-hoist-variables": "^7.14.5", "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/parser": "^7.14.5", - "@babel/types": "^7.14.5", + "@babel/parser": "^7.14.8", + "@babel/types": "^7.14.8", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", + "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "to-fast-properties": "^2.0.0" } } @@ -3675,34 +3675,34 @@ } }, "@babel/generator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz", - "integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.8.tgz", + "integrity": "sha512-cYDUpvIzhBVnMzRoY1fkSEhK/HmwEVwlyULYgn/tMQYd6Obag3ylCjONle3gdErfXBW61SVTlR9QR7uWlgeIkg==", "requires": { - "@babel/types": "^7.14.5", + "@babel/types": "^7.14.8", "jsesc": "^2.5.1", "source-map": "^0.5.0" } }, "@babel/helper-module-transforms": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.5.tgz", - "integrity": "sha512-iXpX4KW8LVODuAieD7MzhNjmM6dzYY5tfRqT+R9HDXWl0jPn/djKmA+G9s/2C2T9zggw5tK1QNqZ70USfedOwA==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.8.tgz", + "integrity": "sha512-RyE+NFOjXn5A9YU1dkpeBaduagTlZ0+fccnIcAGbv1KGUlReBj7utF7oEth8IdIBQPcux0DDgW5MFBH2xu9KcA==", "requires": { "@babel/helper-module-imports": "^7.14.5", "@babel/helper-replace-supers": "^7.14.5", - "@babel/helper-simple-access": "^7.14.5", + "@babel/helper-simple-access": "^7.14.8", "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "@babel/template": "^7.14.5", - "@babel/traverse": "^7.14.5", - "@babel/types": "^7.14.5" + "@babel/traverse": "^7.14.8", + "@babel/types": "^7.14.8" } }, "@babel/parser": { - "version": "7.14.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.6.tgz", - "integrity": "sha512-oG0ej7efjEXxb4UgE+klVx+3j4MVo+A2vCzm7OUN4CLo6WhQ+vSOD2yJ8m7B+DghObxtLxt3EfgMWpq+AsWehQ==" + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.8.tgz", + "integrity": "sha512-syoCQFOoo/fzkWDeM0dLEZi5xqurb5vuyzwIMNZRNun+N/9A4cUZeQaE7dTrB8jGaKuJRBtEOajtnmw0I5hvvA==" }, "@babel/template": { "version": "7.14.5", @@ -3715,36 +3715,36 @@ } }, "@babel/traverse": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.5.tgz", - "integrity": "sha512-G3BiS15vevepdmFqmUc9X+64y0viZYygubAMO8SvBmKARuF6CPSZtH4Ng9vi/lrWlZFGe3FWdXNy835akH8Glg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.8.tgz", + "integrity": "sha512-kexHhzCljJcFNn1KYAQ6A5wxMRzq9ebYpEDV4+WdNyr3i7O44tanbDOR/xjiG2F3sllan+LgwK+7OMk0EmydHg==", "requires": { "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.14.5", + "@babel/generator": "^7.14.8", "@babel/helper-function-name": "^7.14.5", "@babel/helper-hoist-variables": "^7.14.5", "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/parser": "^7.14.5", - "@babel/types": "^7.14.5", + "@babel/parser": "^7.14.8", + "@babel/types": "^7.14.8", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", + "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "to-fast-properties": "^2.0.0" } } } }, "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.14.5.tgz", - "integrity": "sha512-+Xe5+6MWFo311U8SchgeX5c1+lJM+eZDBZgD+tvXu9VVQPXwwVzeManMMjYX6xw2HczngfOSZjoFYKwdeB/Jvw==", + "version": "7.14.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.14.7.tgz", + "integrity": "sha512-DTNOTaS7TkW97xsDMrp7nycUVh6sn/eq22VaxWfEdzuEbRsiaOU0pqU7DlyUGHVsbQbSghvjKRpEl+nUCKGQSg==", "requires": { "@babel/helper-create-regexp-features-plugin": "^7.14.5" } @@ -3940,11 +3940,11 @@ }, "dependencies": { "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.8.tgz", + "integrity": "sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.8", "to-fast-properties": "^2.0.0" } }, @@ -3986,9 +3986,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001237", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001237.tgz", - "integrity": "sha512-pDHgRndit6p1NR2GhzMbQ6CkRrp4VKuSsqbcLeOQppYPKOYkKT/6ZvZDvKJUqcmtyWIAHuZq3SVS2vc1egCZzw==" + "version": "1.0.30001248", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001248.tgz", + "integrity": "sha512-NwlQbJkxUFJ8nMErnGtT0QTM2TJ33xgz4KXJSMIrjXIbDVdaYueGyjOrLKRtJC+rTiWfi6j5cnZN1NBiSBJGNw==" }, "chalk": { "version": "2.4.2", @@ -4019,9 +4019,9 @@ "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==" }, "core-js-compat": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.14.0.tgz", - "integrity": "sha512-R4NS2eupxtiJU+VwgkF9WTpnSfZW4pogwKHd8bclWU2sp93Pr5S1uYJI84cMOubJRou7bcfL0vmwtLslWN5p3A==", + "version": "3.15.2", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.15.2.tgz", + "integrity": "sha512-Wp+BJVvwopjI+A1EFqm2dwUmWYXrvucmtIB2LgXn/Rb+gWPKYxtmb4GKHGKG/KGF1eK9jfjzT38DITbTOCX/SQ==", "requires": { "browserslist": "^4.16.6", "semver": "7.0.0" @@ -4035,9 +4035,9 @@ } }, "electron-to-chromium": { - "version": "1.3.752", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.752.tgz", - "integrity": "sha512-2Tg+7jSl3oPxgsBsWKh5H83QazTkmWG/cnNwJplmyZc7KcN61+I10oUgaXSVk/NwfvN3BdkKDR4FYuRBQQ2v0A==" + "version": "1.3.791", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.791.tgz", + "integrity": "sha512-Tdx7w1fZpeWOOBluK+kXTAKCXyc79K65RB6Zp0+sPSZZhDjXlrxfGlXrlMGVVQUrKCyEZFQs1UBBLNz5IdbF0g==" }, "fs-extra": { "version": "9.0.0", @@ -15482,9 +15482,9 @@ } }, "@unimodules/core": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@unimodules/core/-/core-7.1.0.tgz", - "integrity": "sha512-oLRT4Bkah3GEopkxmTgpHsRTRp+NJ1907ZjE9y/HLh32q7O/3mcbpY77Uvm+EXW0Vh14gOlU+bmkpC0hz3we0w==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@unimodules/core/-/core-7.1.1.tgz", + "integrity": "sha512-Sa7X+WkrhZzcckavjuQu4mq6BTPWsio7OITfoNNzjL0CEmfHfo3DNWQWoVyj+wCgMnjGUT1l3+q3AQlI+CaCLA==", "requires": { "compare-versions": "^3.4.0" } @@ -23151,9 +23151,9 @@ } }, "expo-asset": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-8.3.2.tgz", - "integrity": "sha512-MKOwkkN0lnQRcOdn5moqkHPmLgFoUSIYyrvMAJ767vTXvLvZgoQgvBwqCAXsXitIwEitG0Az3XZ23SfKJpFbFg==", + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-8.3.3.tgz", + "integrity": "sha512-qCm5d14tzswY8DcmRJ+0WkY9tc3OiVikBAiw2hCMC+bFpK/bEdqy4Zwfd69MFIAJ0taJpHWhdUoBRO0byQLlfg==", "requires": { "blueimp-md5": "^2.10.0", "invariant": "^2.2.4", @@ -38388,9 +38388,9 @@ "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=" }, "slugify": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.5.3.tgz", - "integrity": "sha512-/HkjRdwPY3yHJReXu38NiusZw2+LLE2SrhkWJtmlPDB1fqFSvioYj62NkPcrKiNCgRLeGcGK7QBvr1iQwybeXw==" + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.0.tgz", + "integrity": "sha512-FkMq+MQc5hzYgM86nLuHI98Acwi3p4wX+a5BO9Hhw4JdK4L7WueIiZ4tXEobImPqBz2sVcV0+Mu3GRB30IGang==" }, "snapdragon": { "version": "0.8.2", diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index dfe7cea28c1b0..b1ef5edb44c96 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -81,7 +81,7 @@ class ReportScreen extends React.Component { this.setState({isLoading: true}); clearTimeout(this.loadingTimerId); - this.loadingTimerId = setTimeout(() => this.setState({isLoading: false}), 150); + this.loadingTimerId = setTimeout(() => this.setState({isLoading: false}), 0); } /** @@ -104,7 +104,7 @@ class ReportScreen extends React.Component { onNavigationMenuButtonClicked={() => Navigation.navigate(ROUTES.HOME)} /> - + {/* */} {!this.shouldShowLoader() && } diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index 7f5f73b205855..c3fbb5390442b 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -4,6 +4,7 @@ import { Keyboard, AppState, ActivityIndicator, + Platform, } from 'react-native'; import PropTypes from 'prop-types'; import _ from 'underscore'; From 46f350af0165f01d28f230c7ccc515445d24dde5 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Thu, 29 Jul 2021 14:04:04 -1000 Subject: [PATCH 76/84] meh --- src/pages/home/ReportScreen.js | 2 +- src/pages/home/report/ReportActionsView.js | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index b1ef5edb44c96..af2bf24898577 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -81,7 +81,7 @@ class ReportScreen extends React.Component { this.setState({isLoading: true}); clearTimeout(this.loadingTimerId); - this.loadingTimerId = setTimeout(() => this.setState({isLoading: false}), 0); + this.loadingTimerId = setTimeout(() => this.setState({isLoading: false}), 150); } /** diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index c3fbb5390442b..7f5f73b205855 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -4,7 +4,6 @@ import { Keyboard, AppState, ActivityIndicator, - Platform, } from 'react-native'; import PropTypes from 'prop-types'; import _ from 'underscore'; From b73292cdc0dd29ed5ef197ff3bc7188d2b4a6cfe Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Thu, 29 Jul 2021 14:04:42 -1000 Subject: [PATCH 77/84] meh --- src/pages/home/ReportScreen.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index af2bf24898577..dfe7cea28c1b0 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -104,7 +104,7 @@ class ReportScreen extends React.Component { onNavigationMenuButtonClicked={() => Navigation.navigate(ROUTES.HOME)} /> - {/* */} + {!this.shouldShowLoader() && } From 95549dea66ad374f9ac5ebd591470b2fbe6a0080 Mon Sep 17 00:00:00 2001 From: Joe Gambino Date: Thu, 29 Jul 2021 17:39:42 -0700 Subject: [PATCH 78/84] Send emailList as string --- src/libs/actions/Policy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js index 7c96603338999..ad125f56413b6 100644 --- a/src/libs/actions/Policy.js +++ b/src/libs/actions/Policy.js @@ -116,7 +116,7 @@ function removeMembers(members, policyID) { // Make the API call to merge the login into the policy API.Policy_Employees_Remove({ - emailList: members, + emailList: members.join(','), policyID, }) .then((data) => { From 017ebd856cbbc324d59310c6f309c791c8b7c53d Mon Sep 17 00:00:00 2001 From: Joe Gambino Date: Thu, 29 Jul 2021 17:49:36 -0700 Subject: [PATCH 79/84] update comment --- src/libs/actions/Policy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js index ad125f56413b6..4965508c8b166 100644 --- a/src/libs/actions/Policy.js +++ b/src/libs/actions/Policy.js @@ -114,7 +114,7 @@ function removeMembers(members, policyID) { // Optimistically remove the members from the policy Onyx.set(key, policy); - // Make the API call to merge the login into the policy + // Make the API call to remove a login from the policy API.Policy_Employees_Remove({ emailList: members.join(','), policyID, From 79f9648f180cd3c251986b24ac60c7d2f46a93eb Mon Sep 17 00:00:00 2001 From: Jasper Huang Date: Thu, 29 Jul 2021 19:00:12 -0700 Subject: [PATCH 80/84] Revert "Remove duplicate checks" This reverts commit 8b291cc563bc3274e75247066c0879c72f8d7ef6. --- src/pages/ReimbursementAccount/CompanyStep.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/pages/ReimbursementAccount/CompanyStep.js b/src/pages/ReimbursementAccount/CompanyStep.js index 47b6b46b4ba88..c0fc39effba9b 100644 --- a/src/pages/ReimbursementAccount/CompanyStep.js +++ b/src/pages/ReimbursementAccount/CompanyStep.js @@ -65,11 +65,21 @@ class CompanyStep extends React.Component { * @returns {Boolean} */ validate() { + if (!this.state.password.trim()) { + Growl.error(this.props.translate('common.passwordCannotBeBlank')); + return false; + } + if (!isValidAddress(this.state.addressStreet)) { Growl.error(this.props.translate('bankAccount.error.addressStreet')); return false; } + if (this.state.addressState === '') { + Growl.error(this.props.translate('bankAccount.error.addressState')); + return false; + } + if (!isValidZipCode(this.state.addressZipCode)) { Growl.error(this.props.translate('bankAccount.error.zipCode')); return false; From aba7c445c0b2c686c3465a333802ae977366103f Mon Sep 17 00:00:00 2001 From: Clem Dal Palu Date: Fri, 30 Jul 2021 11:41:46 +0200 Subject: [PATCH 81/84] Copy change for personal address to avoid drop addresses --- src/languages/en.js | 2 +- src/languages/es.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/languages/en.js b/src/languages/en.js index b2969dde44106..d1f286c676823 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -43,7 +43,7 @@ export default { here: 'here', dob: 'Date of Birth', ssnLast4: 'Last 4 Digits of SSN', - addressNoPO: 'Address (no P.O. boxes)', + addressNoPO: 'Personal Address (PO Boxes and mail drop addresses are NOT allowed)', companyAddressNoPO: 'Company Address (PO Boxes and mail drop addresses are NOT allowed)', city: 'City', state: 'State', diff --git a/src/languages/es.js b/src/languages/es.js index 2df2d37624f65..9c521e08474c5 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -43,7 +43,7 @@ export default { here: 'aquí', dob: 'Fecha de Nacimiento', ssnLast4: 'Últimos 4 dígitos de su SSN', - addressNoPO: 'Dirección (sin Apartado Postal)', + addressNoPO: 'Dirección física personal (no se aceptan apartados ni direcciones postales)', companyAddressNoPO: 'Dirección física de la empresa (no se aceptan apartados ni direcciones postales)', city: 'Ciudad', state: 'Estado', From 7c7475b7b7e8cf4534830dc1a347000c9a84f5ec Mon Sep 17 00:00:00 2001 From: Aman Ansari Date: Fri, 30 Jul 2021 20:36:50 +0530 Subject: [PATCH 82/84] minor update --- src/pages/settings/Profile/ProfilePage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/settings/Profile/ProfilePage.js b/src/pages/settings/Profile/ProfilePage.js index c8d7e5ba0b092..9190dc420bf99 100755 --- a/src/pages/settings/Profile/ProfilePage.js +++ b/src/pages/settings/Profile/ProfilePage.js @@ -172,8 +172,8 @@ class ProfilePage extends Component { } = this.state; setPersonalDetails({ - firstName, - lastName, + firstName: firstName.trim(), + lastName: lastName.trim(), pronouns: pronouns === this.props.translate('pronouns.selfSelect') ? selfSelectedPronouns : pronouns, From 68d4c213afabad5ed17a691468173997b35cb3eb Mon Sep 17 00:00:00 2001 From: OSBotify Date: Fri, 30 Jul 2021 17:59:48 +0000 Subject: [PATCH 83/84] Update version to 1.0.81-4 --- android/app/build.gradle | 4 ++-- ios/ExpensifyCash/Info.plist | 2 +- ios/ExpensifyCashTests/Info.plist | 2 +- package-lock.json | 2 +- package.json | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 7468a78780921..985385d9aa7e2 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -150,8 +150,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001008103 - versionName "1.0.81-3" + versionCode 1001008104 + versionName "1.0.81-4" } splits { abi { diff --git a/ios/ExpensifyCash/Info.plist b/ios/ExpensifyCash/Info.plist index ed8938afed34a..cf17a6eb03011 100644 --- a/ios/ExpensifyCash/Info.plist +++ b/ios/ExpensifyCash/Info.plist @@ -30,7 +30,7 @@ CFBundleVersion - 1.0.81.3 + 1.0.81.4 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/ExpensifyCashTests/Info.plist b/ios/ExpensifyCashTests/Info.plist index ac439ff2064ba..194967d37a2a2 100644 --- a/ios/ExpensifyCashTests/Info.plist +++ b/ios/ExpensifyCashTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.0.81.3 + 1.0.81.4 diff --git a/package-lock.json b/package-lock.json index 110742c8d0944..583c8da740ad8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "expensify.cash", - "version": "1.0.81-3", + "version": "1.0.81-4", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 311399383b39c..2b1c900f7de8b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "expensify.cash", - "version": "1.0.81-3", + "version": "1.0.81-4", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "Expensify.cash is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From ef7bcf2f67bd17029ff6c9fea72b0898e51fde86 Mon Sep 17 00:00:00 2001 From: OSBotify Date: Fri, 30 Jul 2021 19:42:18 +0000 Subject: [PATCH 84/84] Update version to 1.0.81-5 --- android/app/build.gradle | 4 ++-- ios/ExpensifyCash/Info.plist | 2 +- ios/ExpensifyCashTests/Info.plist | 2 +- package-lock.json | 2 +- package.json | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 985385d9aa7e2..b44828db2e7d1 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -150,8 +150,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001008104 - versionName "1.0.81-4" + versionCode 1001008105 + versionName "1.0.81-5" } splits { abi { diff --git a/ios/ExpensifyCash/Info.plist b/ios/ExpensifyCash/Info.plist index cf17a6eb03011..2c30175257a7d 100644 --- a/ios/ExpensifyCash/Info.plist +++ b/ios/ExpensifyCash/Info.plist @@ -30,7 +30,7 @@ CFBundleVersion - 1.0.81.4 + 1.0.81.5 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/ExpensifyCashTests/Info.plist b/ios/ExpensifyCashTests/Info.plist index 194967d37a2a2..cba2c4e516da9 100644 --- a/ios/ExpensifyCashTests/Info.plist +++ b/ios/ExpensifyCashTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.0.81.4 + 1.0.81.5 diff --git a/package-lock.json b/package-lock.json index 583c8da740ad8..6ea8dfcbe9fbc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "expensify.cash", - "version": "1.0.81-4", + "version": "1.0.81-5", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 2b1c900f7de8b..73d60c3113de5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "expensify.cash", - "version": "1.0.81-4", + "version": "1.0.81-5", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "Expensify.cash is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.",