From 30adf9a10cd44c7125897ddb4035c43b939893a4 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Mon, 26 Jun 2023 17:34:14 +0200 Subject: [PATCH 001/469] fix: nostrikethrough prop draft solution --- .../HTMLRenderers/EditedRenderer.js | 22 ++++++----- .../applyStrikethrough/index.js | 5 --- .../applyStrikethrough/index.native.js | 8 ---- src/components/OfflineWithFeedback.js | 2 +- src/pages/home/report/ReportActionItem.js | 1 + .../home/report/ReportActionItemFragment.js | 39 +++++++++++-------- src/styles/styles.js | 1 + 7 files changed, 37 insertions(+), 41 deletions(-) delete mode 100644 src/components/HTMLEngineProvider/applyStrikethrough/index.js delete mode 100644 src/components/HTMLEngineProvider/applyStrikethrough/index.native.js diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.js index d91510c3ec6a..4ea53287f756 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.js @@ -17,21 +17,23 @@ function EditedRenderer(props) { const defaultRendererProps = _.omit(props, ['TDefaultRenderer', 'style', 'tnode']); const isPendingDelete = Boolean(props.tnode.attributes.deleted !== undefined); return ( - - {/* Native devices do not support margin between nested text */} + {' '} - {props.translate('reportActionCompose.edited')} + + {/* Native devices do not support margin between nested text */} + {props.translate('reportActionCompose.edited')} + ); } diff --git a/src/components/HTMLEngineProvider/applyStrikethrough/index.js b/src/components/HTMLEngineProvider/applyStrikethrough/index.js deleted file mode 100644 index b754a37dbde9..000000000000 --- a/src/components/HTMLEngineProvider/applyStrikethrough/index.js +++ /dev/null @@ -1,5 +0,0 @@ -function applyStrikethrough(html) { - return html; -} - -export default applyStrikethrough; diff --git a/src/components/HTMLEngineProvider/applyStrikethrough/index.native.js b/src/components/HTMLEngineProvider/applyStrikethrough/index.native.js deleted file mode 100644 index 266e4eb20f62..000000000000 --- a/src/components/HTMLEngineProvider/applyStrikethrough/index.native.js +++ /dev/null @@ -1,8 +0,0 @@ -function applyStrikethrough(html, isPendingDelete) { - if (isPendingDelete) { - return `${html}`; - } - return html; -} - -export default applyStrikethrough; diff --git a/src/components/OfflineWithFeedback.js b/src/components/OfflineWithFeedback.js index 5cb5fc57dc24..120c0d092453 100644 --- a/src/components/OfflineWithFeedback.js +++ b/src/components/OfflineWithFeedback.js @@ -97,7 +97,7 @@ function OfflineWithFeedback(props) { const isUpdateOrDeleteError = hasErrors && (props.pendingAction === 'delete' || props.pendingAction === 'update'); const isAddError = hasErrors && props.pendingAction === 'add'; const needsOpacity = (isOfflinePendingAction && !isUpdateOrDeleteError) || isAddError; - const needsStrikeThrough = props.network.isOffline && props.pendingAction === 'delete'; + const needsStrikeThrough = !props.noStrikeThrough && props.network.isOffline && props.pendingAction === 'delete'; const hideChildren = props.shouldHideOnDelete && !props.network.isOffline && props.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && !hasErrors; let children = props.children; diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 24be64dc6468..442086c7c5da 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -480,6 +480,7 @@ function ReportActionItem(props) { errors={props.action.errors} errorRowStyles={[styles.ml10, styles.mr2]} needsOffscreenAlphaCompositing={ReportActionsUtils.isMoneyRequestAction(props.action)} + noStrikeThrough > {isWhisper && ( diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js index 65558110ba45..edf8955021d1 100644 --- a/src/pages/home/report/ReportActionItemFragment.js +++ b/src/pages/home/report/ReportActionItemFragment.js @@ -16,7 +16,6 @@ import compose from '../../../libs/compose'; import convertToLTR from '../../../libs/convertToLTR'; import {withNetwork} from '../../../components/OnyxProvider'; import CONST from '../../../CONST'; -import applyStrikethrough from '../../../components/HTMLEngineProvider/applyStrikethrough'; import editedLabelStyles from '../../../styles/editedLabelStyles'; import UserDetailsTooltip from '../../../components/UserDetailsTooltip'; @@ -95,6 +94,7 @@ function ReportActionItemFragment(props) { ); } const {html, text} = props.fragment; + const isPendingDelete = props.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && props.network.isOffline; // Threaded messages display "[Deleted message]" instead of being hidden altogether. // While offline we display the previous message with a strikethrough style. Once online we want to @@ -111,34 +111,39 @@ function ReportActionItemFragment(props) { // Only render HTML if we have html in the fragment if (!differByLineBreaksOnly) { - const isPendingDelete = props.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && props.network.isOffline; const editedTag = props.fragment.isEdited ? `` : ''; - const htmlContent = applyStrikethrough(html + editedTag, isPendingDelete); + const htmlContent = isPendingDelete ? `${html}` : html - return ${htmlContent}` : `${htmlContent}`} />; + const htmlWithTag = editedTag ? `${htmlContent}${editedTag}` : htmlContent; + + return ${htmlWithTag}` : `${htmlWithTag}`} />; } const containsOnlyEmojis = EmojiUtils.containsOnlyEmojis(text); return ( - - {convertToLTR(text)} + + + {convertToLTR(text)} + {Boolean(props.fragment.isEdited) && ( - + <> {' '} - {props.translate('reportActionCompose.edited')} - + + {props.translate('reportActionCompose.edited')} + + )} ); diff --git a/src/styles/styles.js b/src/styles/styles.js index 74066af5e20a..7c4944476195 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -77,6 +77,7 @@ const webViewStyles = { del: { textDecorationLine: 'line-through', textDecorationStyle: 'solid', + flex: 1, }, strong: { From 8548ac7ecbb6d1aebb3c97139f9c64ebae83707e Mon Sep 17 00:00:00 2001 From: Puneet Date: Tue, 4 Jul 2023 05:02:02 +0530 Subject: [PATCH 002/469] add new translation keys --- src/languages/en.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/languages/en.js b/src/languages/en.js index f6989d32f35a..e5fcffe78e3d 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -391,6 +391,7 @@ export default { uploadPhoto: 'Upload photo', removePhoto: 'Remove photo', editImage: 'Edit photo', + viewPhoto: 'View photo', imageUploadFailed: 'Image upload failed', deleteWorkspaceError: 'Sorry, there was an unexpected problem deleting your workspace avatar.', sizeExceeded: ({maxUploadSizeInMB}) => `The selected image exceeds the maximum upload size of ${maxUploadSizeInMB}MB.`, @@ -411,6 +412,7 @@ export default { online: 'Online', offline: 'Offline', syncing: 'Syncing', + profileAvatar: 'Profile avatar', }, loungeAccessPage: { loungeAccess: 'Lounge access', @@ -1090,6 +1092,7 @@ export default { memberNotFound: 'Member not found. To invite a new member to the workspace, please use the Invite button above.', notAuthorized: `You do not have access to this page. Are you trying to join the workspace? Please reach out to the owner of this workspace so they can add you as a member! Something else? Reach out to ${CONST.EMAIL.CONCIERGE}`, goToRoom: ({roomName}) => `Go to ${roomName} room`, + workspaceAvatar: 'Workspace avatar', }, emptyWorkspace: { title: 'Create a new workspace', From 7e39af512130d011e4ff4fe271df52fb9e421c49 Mon Sep 17 00:00:00 2001 From: Puneet Date: Tue, 4 Jul 2023 05:02:35 +0530 Subject: [PATCH 003/469] Update es.js --- src/languages/es.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/languages/es.js b/src/languages/es.js index f765ce0270df..281c81b3ad59 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -390,6 +390,7 @@ export default { uploadPhoto: 'Subir foto', removePhoto: 'Eliminar foto', editImage: 'Editar foto', + viewPhoto: 'Ver foto', imageUploadFailed: 'Error al cargar la imagen', deleteWorkspaceError: 'Lo sentimos, hubo un problema eliminando el avatar de su espacio de trabajo.', sizeExceeded: ({maxUploadSizeInMB}) => `La imagen supera el tamaño máximo de ${maxUploadSizeInMB}MB.`, @@ -410,6 +411,7 @@ export default { online: 'En línea', offline: 'Desconectado', syncing: 'Sincronizando', + profileAvatar: 'Perfil avatar', }, loungeAccessPage: { loungeAccess: 'Acceso a la sala vip', @@ -1097,6 +1099,7 @@ export default { memberNotFound: 'Miembro no encontrado. Para invitar a un nuevo miembro al espacio de trabajo, por favor, utiliza el botón Invitar que está arriba.', notAuthorized: `No tienes acceso a esta página. ¿Estás tratando de unirte al espacio de trabajo? Comunícate con el propietario de este espacio de trabajo para que pueda agregarte como miembro. ¿Necesitas algo más? Comunícate con ${CONST.EMAIL.CONCIERGE}`, goToRoom: ({roomName}) => `Ir a la sala ${roomName}`, + workspaceAvatar: 'Espacio de trabajo avatar', }, emptyWorkspace: { title: 'Crear un nuevo espacio de trabajo', From 08a930233f9d74969ea20e5ef464bbebab1ca453 Mon Sep 17 00:00:00 2001 From: Puneet Date: Tue, 4 Jul 2023 05:08:42 +0530 Subject: [PATCH 004/469] add avatar preview option --- src/components/AvatarWithImagePicker.js | 131 ++++++++++++++---------- 1 file changed, 79 insertions(+), 52 deletions(-) diff --git a/src/components/AvatarWithImagePicker.js b/src/components/AvatarWithImagePicker.js index 45e75aaeec57..74a28c3821c9 100644 --- a/src/components/AvatarWithImagePicker.js +++ b/src/components/AvatarWithImagePicker.js @@ -22,6 +22,7 @@ import stylePropTypes from '../styles/stylePropTypes'; import * as FileUtils from '../libs/fileDownload/FileUtils'; import getImageResolution from '../libs/fileDownload/getImageResolution'; import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback'; +import AttachmentModal from './AttachmentModal'; const propTypes = { /** Avatar source to display */ @@ -78,6 +79,15 @@ const propTypes = { // eslint-disable-next-line react/forbid-prop-types errors: PropTypes.object, + /** Title for avatar preview modal */ + headerTitle: PropTypes.string, + + /** Avatar source for avatar preview modal */ + previewSource: PropTypes.string, + + /** File name of the avatar */ + originalFileName: PropTypes.string, + ...withLocalizePropTypes, }; @@ -97,6 +107,9 @@ const defaultProps = { onErrorClose: () => {}, pendingAction: null, errors: null, + headerTitle: '', + previewSource: '', + originalFileName: '', }; class AvatarWithImagePicker extends React.Component { @@ -241,9 +254,10 @@ class AvatarWithImagePicker extends React.Component { * Create menu items list for avatar menu * * @param {Function} openPicker + * @param {Function} show * @returns {Array} */ - createMenuItems(openPicker) { + createMenuItems(openPicker, show) { const menuItems = [ { icon: Expensicons.Upload, @@ -265,6 +279,11 @@ class AvatarWithImagePicker extends React.Component { this.props.onImageRemoved(); }, }); + menuItems.unshift({ + icon: Expensicons.Eye, + text: this.props.translate('avatarWithImagePicker.viewPhoto'), + onSelected: () => show(), + }); } return menuItems; } @@ -275,62 +294,70 @@ class AvatarWithImagePicker extends React.Component { return ( - this.setState({isMenuVisible: true})} - accessibilityRole="button" - accessibilityLabel={this.props.translate('avatarWithImagePicker.editImage')} + - - ( + this.setState({isMenuVisible: true})} + accessibilityRole="button" + accessibilityLabel={this.props.translate('avatarWithImagePicker.editImage')} > - - - {this.props.source ? ( - - ) : ( - - )} - - - - - - {({openPicker}) => ( - <> + + - - + + {this.props.source ? ( + + ) : ( + + )} - this.setState({isMenuVisible: false})} - onItemSelected={() => this.setState({isMenuVisible: false})} - menuItems={this.createMenuItems(openPicker)} - anchorPosition={this.props.anchorPosition} - anchorAlignment={this.props.anchorAlignment} - /> - - )} - - - + + + + {({openPicker}) => ( + <> + + + + + + this.setState({isMenuVisible: false})} + onItemSelected={() => this.setState({isMenuVisible: false})} + menuItems={this.createMenuItems(openPicker, show)} + anchorPosition={this.props.anchorPosition} + anchorAlignment={this.props.anchorAlignment} + /> + + )} + + + + )} + Date: Tue, 4 Jul 2023 05:10:09 +0530 Subject: [PATCH 005/469] make profile avatar viewable --- src/pages/settings/Profile/ProfilePage.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/pages/settings/Profile/ProfilePage.js b/src/pages/settings/Profile/ProfilePage.js index 07db3d0cdffb..9a1aefb4e2c3 100755 --- a/src/pages/settings/Profile/ProfilePage.js +++ b/src/pages/settings/Profile/ProfilePage.js @@ -60,6 +60,8 @@ function ProfilePage(props) { }; const currentUserDetails = props.currentUserPersonalDetails || {}; const contactMethodBrickRoadIndicator = UserUtils.getLoginListBrickRoadIndicator(props.loginList); + const avatarURL = lodashGet(currentUserDetails, 'avatar', ''); + const accountID = lodashGet(currentUserDetails, 'accountID', ''); const profileSettingsOptions = [ { @@ -98,7 +100,7 @@ function ProfilePage(props) { {_.map(profileSettingsOptions, (detail, index) => ( From 5643d7451818448c88b4b9af8d555d8fd46d74f8 Mon Sep 17 00:00:00 2001 From: Puneet Date: Tue, 4 Jul 2023 05:12:43 +0530 Subject: [PATCH 006/469] add orginalFileName --- src/libs/actions/Policy.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js index bad48978b0e1..ea6d1cb38942 100644 --- a/src/libs/actions/Policy.js +++ b/src/libs/actions/Policy.js @@ -426,6 +426,7 @@ function updateWorkspaceAvatar(policyID, file) { key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { avatar: file.uri, + originalFileName: file.name, errorFields: { avatar: null, }, From d2ca2249e1d8c363304b7cec8efdc96935567253 Mon Sep 17 00:00:00 2001 From: Puneet Date: Tue, 4 Jul 2023 05:13:53 +0530 Subject: [PATCH 007/469] add avatar preview feature at workpace page --- src/pages/workspace/WorkspaceSettingsPage.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/pages/workspace/WorkspaceSettingsPage.js b/src/pages/workspace/WorkspaceSettingsPage.js index 6abde456f40b..4aeebfbd54b3 100644 --- a/src/pages/workspace/WorkspaceSettingsPage.js +++ b/src/pages/workspace/WorkspaceSettingsPage.js @@ -20,6 +20,7 @@ import {withNetwork} from '../../components/OnyxProvider'; import OfflineWithFeedback from '../../components/OfflineWithFeedback'; import Form from '../../components/Form'; import * as ReportUtils from '../../libs/ReportUtils'; +import * as UserUtils from '../../libs/UserUtils'; import Avatar from '../../components/Avatar'; import Navigation from '../../libs/Navigation/Navigation'; import ROUTES from '../../ROUTES'; @@ -125,6 +126,9 @@ function WorkspaceSettingsPage(props) { pendingAction={lodashGet(props.policy, 'pendingFields.avatar', null)} errors={lodashGet(props.policy, 'errorFields.avatar', null)} onErrorClose={() => Policy.clearAvatarErrors(props.policy.id)} + previewSource={UserUtils.getFullSizeAvatar(props.policy.avatar, '')} + headerTitle={props.translate('workspace.common.workspaceAvatar')} + originalFileName={props.policy.originalFileName} /> Date: Sat, 22 Jul 2023 10:09:53 +0500 Subject: [PATCH 008/469] Added recovery code feature --- src/CONST.js | 5 + src/languages/en.js | 9 ++ src/languages/es.js | 9 ++ src/libs/ValidationUtils.js | 5 + .../ValidateCodeForm/BaseValidateCodeForm.js | 117 ++++++++++++++---- 5 files changed, 121 insertions(+), 24 deletions(-) diff --git a/src/CONST.js b/src/CONST.js index 46aa9a1943e9..042728e3ddcb 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -760,6 +760,9 @@ const CONST = { // 6 numeric digits VALIDATE_CODE_REGEX_STRING: /^\d{6}$/, + // 8 alphanumeric characters + RECOVERY_CODE_REGEX_STRING: /^[a-zA-Z0-9]{8}$/, + // The server has a WAF (Web Application Firewall) which will strip out HTML/XML tags using this regex pattern. // It's copied here so that the same regex pattern can be used in form validations to be consistent with the server. VALIDATE_FOR_HTML_TAG_REGEX: /<([^>\s]+)(?:[^>]*?)>/g, @@ -801,6 +804,8 @@ const CONST = { MAGIC_CODE_LENGTH: 6, MAGIC_CODE_EMPTY_CHAR: ' ', + RECOVERY_CODE_LENGTH: 8, + KEYBOARD_TYPE: { PHONE_PAD: 'phone-pad', NUMBER_PAD: 'number-pad', diff --git a/src/languages/en.js b/src/languages/en.js index 47cc8d209735..3e01f8ceb722 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -567,6 +567,15 @@ export default { copy: 'Copy', disable: 'Disable', }, + recoveryCodeForm: { + error: { + pleaseFillRecoveryCode: 'Please enter your recovery code', + incorrectRecoveryCode: 'Incorrect recovery code. Please try again.', + }, + useRecoveryCode: 'Use recovery code', + recoveryCode: 'Recovery code', + use2fa: 'Use two-factor authentication code', + }, twoFactorAuthForm: { error: { pleaseFillTwoFactorAuth: 'Please enter your two-factor authentication code', diff --git a/src/languages/es.js b/src/languages/es.js index 5eea74099e4c..7c112f5658ff 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -568,6 +568,15 @@ export default { copy: 'Copiar', disable: 'Deshabilitar', }, + recoveryCodeForm: { + error: { + pleaseFillRecoveryCode: 'Por favor, introduce tu código de recuperación', + incorrectRecoveryCode: 'Código de recuperación incorrecto. Por favor, inténtalo de nuevo', + }, + useRecoveryCode: 'Usar código de recuperación', + recoveryCode: 'Código de recuperación', + use2fa: 'Usar autenticación de dos factores', + }, twoFactorAuthForm: { error: { pleaseFillTwoFactorAuth: 'Por favor, introduce tu código de autenticación de dos factores', diff --git a/src/libs/ValidationUtils.js b/src/libs/ValidationUtils.js index c120f649b401..229813c64dd6 100644 --- a/src/libs/ValidationUtils.js +++ b/src/libs/ValidationUtils.js @@ -307,6 +307,10 @@ function isValidValidateCode(validateCode) { return validateCode.match(CONST.VALIDATE_CODE_REGEX_STRING); } +function isValidRecoveryCode(recoveryCode) { + return recoveryCode.match(CONST.RECOVERY_CODE_REGEX_STRING); +} + /** * @param {String} code * @returns {Boolean} @@ -478,4 +482,5 @@ export { doesContainReservedWord, isNumeric, isValidAccountRoute, + isValidRecoveryCode, }; diff --git a/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js b/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js index 9abbf5ea4957..f211f154a8a1 100755 --- a/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js +++ b/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js @@ -26,6 +26,7 @@ import Terms from '../Terms'; import PressableWithFeedback from '../../../components/Pressable/PressableWithFeedback'; import usePrevious from '../../../hooks/usePrevious'; import * as StyleUtils from '../../../styles/StyleUtils'; +import TextInput from '../../../components/TextInput'; const propTypes = { /* Onyx Props */ @@ -77,6 +78,8 @@ function BaseValidateCodeForm(props) { const [validateCode, setValidateCode] = useState(props.credentials.validateCode || ''); const [twoFactorAuthCode, setTwoFactorAuthCode] = useState(''); const [timeRemaining, setTimeRemaining] = useState(30); + const [isUsingRecoveryCode, setIsUsingRecoveryCode] = useState(false); + const [recoveryCode, setRecoveryCode] = useState(''); const prevRequiresTwoFactorAuth = usePrevious(props.account.requiresTwoFactorAuth); const prevValidateCode = usePrevious(props.credentials.validateCode); @@ -148,7 +151,17 @@ function BaseValidateCodeForm(props) { * @param {String} key */ const onTextInput = (text, key) => { - const setInput = key === 'validateCode' ? setValidateCode : setTwoFactorAuthCode; + let setInput; + if (key === 'validateCode') { + setInput = setValidateCode; + } + if (key === 'twoFactorAuthCode') { + setInput = setTwoFactorAuthCode; + } + if (key === 'recoveryCode') { + setInput = setRecoveryCode; + } + setInput(text); setFormError((prevError) => ({...prevError, [key]: ''})); @@ -183,6 +196,22 @@ function BaseValidateCodeForm(props) { Session.clearSignInData(); }; + /** + * Switches between 2fa and recovery code, clears inputs and errors + */ + const switchBetween2faAndRecoveryCode = () => { + setIsUsingRecoveryCode(!isUsingRecoveryCode); + + setRecoveryCode(''); + setTwoFactorAuthCode(''); + + setFormError((prevError) => ({...prevError, recoveryCode: '', twoFactorAuthCode: ''})); + + if (props.account.errors) { + Session.clearAccountMessages(); + } + }; + useEffect(() => { if (!isLoadingResendValidationForm) { return; @@ -199,13 +228,27 @@ function BaseValidateCodeForm(props) { if (input2FARef.current) { input2FARef.current.blur(); } - if (!twoFactorAuthCode.trim()) { - setFormError({twoFactorAuthCode: 'validateCodeForm.error.pleaseFillTwoFactorAuth'}); - return; - } - if (!ValidationUtils.isValidTwoFactorCode(twoFactorAuthCode)) { - setFormError({twoFactorAuthCode: 'passwordForm.error.incorrect2fa'}); - return; + /** + * User could be using either recovery code or 2fa code + */ + if (!isUsingRecoveryCode) { + if (!twoFactorAuthCode.trim()) { + setFormError({twoFactorAuthCode: 'validateCodeForm.error.pleaseFillTwoFactorAuth'}); + return; + } + if (!ValidationUtils.isValidTwoFactorCode(twoFactorAuthCode)) { + setFormError({twoFactorAuthCode: 'passwordForm.error.incorrect2fa'}); + return; + } + } else { + if (!recoveryCode.trim()) { + setFormError({recoveryCode: 'recoveryCodeForm.error.pleaseFillRecoveryCode'}); + return; + } + if (!ValidationUtils.isValidRecoveryCode(recoveryCode)) { + setFormError({recoveryCode: 'recoveryCodeForm.error.incorrectRecoveryCode'}); + return; + } } } else { if (inputValidateCodeRef.current) { @@ -222,33 +265,59 @@ function BaseValidateCodeForm(props) { } setFormError({}); + const recoveryCodeOr2faCode = isUsingRecoveryCode ? recoveryCode : twoFactorAuthCode; + const accountID = lodashGet(props.credentials, 'accountID'); if (accountID) { - Session.signInWithValidateCode(accountID, validateCode, props.preferredLocale, twoFactorAuthCode); + Session.signInWithValidateCode(accountID, validateCode, props.preferredLocale, recoveryCodeOr2faCode); } else { - Session.signIn('', validateCode, twoFactorAuthCode, props.preferredLocale); + Session.signIn('', validateCode, recoveryCodeOr2faCode, props.preferredLocale); } - }, [props.account.requiresTwoFactorAuth, props.credentials, props.preferredLocale, twoFactorAuthCode, validateCode]); + }, [props.account.requiresTwoFactorAuth, props.credentials, props.preferredLocale, twoFactorAuthCode, validateCode, isUsingRecoveryCode, recoveryCode]); return ( <> {/* At this point, if we know the account requires 2FA we already successfully authenticated */} {props.account.requiresTwoFactorAuth ? ( - onTextInput(text, 'twoFactorAuthCode')} - onFulfill={validateAndSubmitForm} - maxLength={CONST.TFA_CODE_LENGTH} - errorText={formError.twoFactorAuthCode ? props.translate(formError.twoFactorAuthCode) : ''} - hasError={hasError} - autoFocus - /> + {isUsingRecoveryCode ? ( + onTextInput(text, 'recoveryCode')} + maxLength={CONST.RECOVERY_CODE_LENGTH} + label={props.translate('recoveryCodeForm.recoveryCode')} + errorText={formError.recoveryCode ? props.translate(formError.recoveryCode) : ''} + hasError={hasError} + autoFocus + /> + ) : ( + onTextInput(text, 'twoFactorAuthCode')} + onFulfill={validateAndSubmitForm} + maxLength={CONST.TFA_CODE_LENGTH} + errorText={formError.twoFactorAuthCode ? props.translate(formError.twoFactorAuthCode) : ''} + hasError={hasError} + autoFocus + /> + )} {hasError && } + + {isUsingRecoveryCode ? props.translate('recoveryCodeForm.use2fa') : props.translate('recoveryCodeForm.useRecoveryCode')} + ) : ( From d21046da995658519255fd53e0c59dcae2b1d508 Mon Sep 17 00:00:00 2001 From: Ali Toshmatov Date: Sat, 22 Jul 2023 10:58:38 +0500 Subject: [PATCH 009/469] Added focus delay so that keyboard behaves smoothlier --- src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js b/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js index f211f154a8a1..458863d1eae0 100755 --- a/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js +++ b/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js @@ -282,6 +282,7 @@ function BaseValidateCodeForm(props) { {isUsingRecoveryCode ? ( onTextInput(text, 'recoveryCode')} @@ -293,6 +294,7 @@ function BaseValidateCodeForm(props) { /> ) : ( Date: Mon, 31 Jul 2023 09:24:15 -0700 Subject: [PATCH 010/469] Add parent reportAction isDeletedParentAction --- src/components/ReportActionItem/TaskPreview.js | 7 +++++-- src/languages/en.js | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/ReportActionItem/TaskPreview.js b/src/components/ReportActionItem/TaskPreview.js index 740ab345fa74..b8b676a7e2b5 100644 --- a/src/components/ReportActionItem/TaskPreview.js +++ b/src/components/ReportActionItem/TaskPreview.js @@ -22,6 +22,7 @@ import * as ReportUtils from '../../libs/ReportUtils'; import RenderHTML from '../RenderHTML'; import PressableWithoutFeedback from '../Pressable/PressableWithoutFeedback'; import personalDetailsPropType from '../../pages/personalDetailsPropType'; +import * as ReportActionsUtils from '../../libs/ReportActionsUtils'; const propTypes = { /** All personal details asssociated with user */ @@ -71,8 +72,10 @@ function TaskPreview(props) { const assigneeDisplayName = lodashGet(props.personalDetailsList, [taskAssigneeAccountID, 'displayName'], ''); const taskAssignee = assigneeLogin || assigneeDisplayName; const htmlForTaskPreview = taskAssignee ? `@${taskAssignee} ${taskTitle}` : `${taskTitle}`; - - return ( + const isDeletedParentAction = ReportActionsUtils.isDeletedParentAction(props.action); + return isDeletedParentAction ? ( + ${props.translate('parentReportAction.deletedTask')}`} /> + ) : ( Navigation.navigate(ROUTES.getReportRoute(props.taskReportID))} diff --git a/src/languages/en.js b/src/languages/en.js index b7a130addf18..42e0de6eb037 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -1503,6 +1503,7 @@ export default { parentReportAction: { deletedMessage: '[Deleted message]', deletedRequest: '[Deleted request]', + deletedTask: '[Deleted task]', hiddenMessage: '[Hidden message]', }, threads: { From 69b67846de69452edd27e79d272f0126c6e54077 Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Tue, 1 Aug 2023 15:00:54 -0700 Subject: [PATCH 011/469] Add isDeletedParentAction check --- src/pages/home/report/ReportActionItem.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 8c6defcb2951..00467a50def0 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -449,8 +449,8 @@ function ReportActionItem(props) { }; if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED) { - const parentReport = ReportActionsUtils.getParentReportAction(props.report); - if (ReportActionsUtils.isTransactionThread(parentReport)) { + const parentReportAction = ReportActionsUtils.getParentReportAction(props.report); + if (ReportActionsUtils.isTransactionThread(parentReportAction)) { return ( ); } - if (ReportUtils.isTaskReport(props.report)) { + if (ReportUtils.isTaskReport(props.report) && !ReportActionsUtils.isDeletedParentAction(parentReportAction)) { return ( Date: Tue, 1 Aug 2023 15:21:07 -0700 Subject: [PATCH 012/469] Update header view on cancelled task --- src/pages/home/HeaderView.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js index 7622a0e73f18..1be79bb6fa92 100644 --- a/src/pages/home/HeaderView.js +++ b/src/pages/home/HeaderView.js @@ -31,6 +31,7 @@ import PinButton from '../../components/PinButton'; import Navigation from '../../libs/Navigation/Navigation'; import ROUTES from '../../ROUTES'; import TaskHeaderActionButton from '../../components/TaskHeaderActionButton'; +import * as ReportActionsUtils from '../../libs/ReportActionsUtils'; const propTypes = { /** Toggles the navigationMenu open and closed */ @@ -94,12 +95,14 @@ function HeaderView(props) { const isConcierge = ReportUtils.hasSingleParticipant(props.report) && _.contains(participants, CONST.ACCOUNT_ID.CONCIERGE); const isAutomatedExpensifyAccount = ReportUtils.hasSingleParticipant(props.report) && ReportUtils.hasAutomatedExpensifyAccountIDs(participants); const guideCalendarLink = lodashGet(props.account, 'guideCalendarLink'); + const parentReportAction = ReportActionsUtils.getParentReportAction(props.report); + const isDeletedParentAction = ReportActionsUtils.isDeletedParentAction(parentReportAction); // We hide the button when we are chatting with an automated Expensify account since it's not possible to contact // these users via alternative means. It is possible to request a call with Concierge so we leave the option for them. const shouldShowCallButton = (isConcierge && guideCalendarLink) || (!isAutomatedExpensifyAccount && !isTaskReport); const threeDotMenuItems = []; - if (isTaskReport) { + if (isTaskReport && !isDeletedParentAction) { const isTaskAssigneeOrTaskOwner = Task.isTaskAssigneeOrTaskOwner(props.report, props.session.accountID); if (ReportUtils.isOpenTaskReport(props.report) && isTaskAssigneeOrTaskOwner) { threeDotMenuItems.push({ @@ -133,7 +136,7 @@ function HeaderView(props) { const defaultSubscriptSize = ReportUtils.isExpenseRequest(props.report) ? CONST.AVATAR_SIZE.SMALL_NORMAL : CONST.AVATAR_SIZE.DEFAULT; const icons = ReportUtils.getIcons(reportHeaderData, props.personalDetails); const brickRoadIndicator = ReportUtils.hasReportNameError(props.report) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''; - const shouldShowBorderBottom = !isTaskReport || !props.isSmallScreenWidth; + const shouldShowBorderBottom = !props.isSmallScreenWidth; return ( Date: Tue, 1 Aug 2023 15:31:47 -0700 Subject: [PATCH 013/469] update the header --- src/libs/ReportUtils.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index f5d417fb0a79..4e400345a544 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1244,8 +1244,8 @@ function getReportPreviewMessage(report, reportAction = {}) { */ function getReportName(report, policy = undefined) { let formattedName; + const parentReportAction = ReportActionsUtils.getParentReportAction(report); if (isChatThread(report)) { - const parentReportAction = ReportActionsUtils.getParentReportAction(report); if (ReportActionsUtils.isTransactionThread(parentReportAction)) { return getTransactionReportName(parentReportAction); } @@ -1263,6 +1263,11 @@ function getReportName(report, policy = undefined) { } return parentReportActionMessage || Localize.translateLocal('parentReportAction.deletedMessage'); } + + if (isTaskReport(report) && ReportActionsUtils.isDeletedParentAction(parentReportAction)) { + return Localize.translateLocal('parentReportAction.deletedTask'); + } + if (isChatRoom(report) || isTaskReport(report)) { formattedName = report.reportName; } From 1c887daf3d549624ad5730c4df44281df68d955a Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Tue, 1 Aug 2023 17:36:03 -0700 Subject: [PATCH 014/469] add method to get all reportActions --- src/libs/ReportActionsUtils.js | 22 ++++++++++++++++++++++ src/libs/actions/Task.js | 1 + 2 files changed, 23 insertions(+) diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index 3cd5621e5e32..d6a59921dc0b 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -561,6 +561,27 @@ function isMessageDeleted(reportAction) { return lodashGet(reportAction, ['message', 0, 'isDeletedParentAction'], false); } +/** + * When you change assignees, a task created reportAction is added to that specific assignee's report. + * You can do this multiple times, so when we perform an action that modifies the parent report, we need to update all the parent report actions + * @param {*} taskReportID + * @returns {Array} taskCreatedReportActions + */ +function getTaskCreatedReportActions(taskReportID) { + const taskCreatedReportActions = []; + _.each(allReports, (report) => { + _.each(report.reportActions, (reportAction) => { + if (reportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT || reportAction.originalMessage.taskReportID !== taskReportID) + { + return; + } + + taskCreatedReportActions.push(reportAction); + }); + }); + return taskCreatedReportActions; +} + export { getSortedReportActions, getLastVisibleAction, @@ -593,4 +614,5 @@ export { isWhisperAction, isPendingRemove, getReportAction, + getTaskCreatedReportActions, }; diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js index 3c6ddefbc32d..6bba6a052e75 100644 --- a/src/libs/actions/Task.js +++ b/src/libs/actions/Task.js @@ -621,6 +621,7 @@ function cancelTask(taskReportID, taskTitle, originalStateNum, originalStatusNum const message = `canceled task: ${taskTitle}`; const optimisticCancelReportAction = ReportUtils.buildOptimisticTaskReportAction(taskReportID, CONST.REPORT.ACTIONS.TYPE.TASKCANCELLED, message); const optimisticReportActionID = optimisticCancelReportAction.reportActionID; + const parentReportAction = ReportActionsUtils.getParentReportAction(taskReportID); const optimisticData = [ { From c9df9ce550c7415b34205b4b95fd3b66cad32334 Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Wed, 2 Aug 2023 16:19:48 -0700 Subject: [PATCH 015/469] update cancelTask method --- src/languages/en.js | 2 +- src/libs/actions/Task.js | 35 +++++++++++++++++++++++++---------- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/languages/en.js b/src/languages/en.js index b6f947a7b0c1..507bd150b3ff 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -1332,7 +1332,7 @@ export default { completed: 'Completed', messages: { completed: 'completed task', - canceled: 'canceled task', + canceled: 'deleted task', reopened: 'reopened task', error: 'You do not have the permission to do the requested action.', }, diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js index 433f555168a9..5dc86635e5c4 100644 --- a/src/libs/actions/Task.js +++ b/src/libs/actions/Task.js @@ -618,20 +618,14 @@ function getShareDestination(reportID, reports, personalDetails) { * @param {number} originalStatusNum */ function cancelTask(taskReportID, taskTitle, originalStateNum, originalStatusNum) { - const message = `canceled task: ${taskTitle}`; + const message = `deleted task: ${taskTitle}`; const optimisticCancelReportAction = ReportUtils.buildOptimisticTaskReportAction(taskReportID, CONST.REPORT.ACTIONS.TYPE.TASKCANCELLED, message); const optimisticReportActionID = optimisticCancelReportAction.reportActionID; - const parentReportAction = ReportActionsUtils.getParentReportAction(taskReportID); + const taskReport = ReportUtils.getReport(taskReportID); + const parentReportAction = ReportActionsUtils.getParentReportAction(taskReport); + const parentReport = ReportUtils.getParentReport(taskReport); const optimisticData = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`, - value: { - stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, - statusNum: CONST.REPORT.STATUS.CLOSED, - }, - }, { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`, @@ -648,6 +642,27 @@ function cancelTask(taskReportID, taskTitle, originalStateNum, originalStatusNum [optimisticReportActionID]: optimisticCancelReportAction, }, }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReport.reportID}`, + value: { + [parentReportAction.reportActionID]: { + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + previousMessage: parentReportAction.message, + message: { + translationKey: '', + type: 'COMMENT', + html: '', + text: '', + isEdited: true, + isDeletedParentAction: true, + }, + errors: null, + linkMetaData: [], + }, + }, + } + ]; const successData = [ From f640ac2bbd8010b262f48d147d24a198ea92d0bf Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Wed, 2 Aug 2023 17:14:38 -0700 Subject: [PATCH 016/469] fix message not showing up --- src/libs/actions/Task.js | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js index 5dc86635e5c4..8c94e1f73c31 100644 --- a/src/libs/actions/Task.js +++ b/src/libs/actions/Task.js @@ -625,6 +625,25 @@ function cancelTask(taskReportID, taskTitle, originalStateNum, originalStatusNum const parentReportAction = ReportActionsUtils.getParentReportAction(taskReport); const parentReport = ReportUtils.getParentReport(taskReport); + const optimisticReportActions = { + [parentReportAction.reportActionID]: { + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + previousMessage: parentReportAction.message, + message: [{ + translationKey: '', + type: 'COMMENT', + html: '', + text: '', + isEdited: true, + isDeletedParentAction: true, + }], + errors: null, + linkMetaData: [], + }, + }; + + // TODO: Figure out some way to get the previous message to show in the LHN for the parent report + const optimisticData = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -633,6 +652,7 @@ function cancelTask(taskReportID, taskTitle, originalStateNum, originalStatusNum lastVisibleActionCreated: optimisticCancelReportAction.created, lastMessageText: message, lastActorAccountID: optimisticCancelReportAction.actorAccountID, + updateReportInLHN: true, }, }, { @@ -645,24 +665,8 @@ function cancelTask(taskReportID, taskTitle, originalStateNum, originalStatusNum { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReport.reportID}`, - value: { - [parentReportAction.reportActionID]: { - pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, - previousMessage: parentReportAction.message, - message: { - translationKey: '', - type: 'COMMENT', - html: '', - text: '', - isEdited: true, - isDeletedParentAction: true, - }, - errors: null, - linkMetaData: [], - }, - }, - } - + value: optimisticReportActions, + }, ]; const successData = [ From 24fc6c8f2356ee8691313f048de644bfcd892b77 Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Wed, 2 Aug 2023 17:40:55 -0700 Subject: [PATCH 017/469] show the parent --- src/components/ReportActionItem/TaskPreview.js | 12 ++++++++---- src/pages/home/report/ReportActionItem.js | 13 ++++++++++++- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/components/ReportActionItem/TaskPreview.js b/src/components/ReportActionItem/TaskPreview.js index b8b676a7e2b5..d8298312df0d 100644 --- a/src/components/ReportActionItem/TaskPreview.js +++ b/src/components/ReportActionItem/TaskPreview.js @@ -73,9 +73,7 @@ function TaskPreview(props) { const taskAssignee = assigneeLogin || assigneeDisplayName; const htmlForTaskPreview = taskAssignee ? `@${taskAssignee} ${taskTitle}` : `${taskTitle}`; const isDeletedParentAction = ReportActionsUtils.isDeletedParentAction(props.action); - return isDeletedParentAction ? ( - ${props.translate('parentReportAction.deletedTask')}`} /> - ) : ( + return ( Navigation.navigate(ROUTES.getReportRoute(props.taskReportID))} @@ -83,7 +81,11 @@ function TaskPreview(props) { accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} accessibilityLabel={props.translate('task.task')} > - + { + isDeletedParentAction ? ( + ${props.translate('parentReportAction.deletedTask')}`} /> + ) : ( + + ) + } ); } - if (ReportUtils.isTaskReport(props.report) && !ReportActionsUtils.isDeletedParentAction(parentReportAction)) { + if (ReportUtils.isTaskReport(props.report)) { + if (ReportActionsUtils.isDeletedParentAction(parentReportAction)) { + return ( + + ) + } + return ( Date: Thu, 3 Aug 2023 15:32:20 -0700 Subject: [PATCH 018/469] Remove the link in Preview isDeletedParentAction --- src/components/ReportActionItem/TaskPreview.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/ReportActionItem/TaskPreview.js b/src/components/ReportActionItem/TaskPreview.js index d8298312df0d..9179858163d8 100644 --- a/src/components/ReportActionItem/TaskPreview.js +++ b/src/components/ReportActionItem/TaskPreview.js @@ -73,6 +73,11 @@ function TaskPreview(props) { const taskAssignee = assigneeLogin || assigneeDisplayName; const htmlForTaskPreview = taskAssignee ? `@${taskAssignee} ${taskTitle}` : `${taskTitle}`; const isDeletedParentAction = ReportActionsUtils.isDeletedParentAction(props.action); + + if (isDeletedParentAction) { + return ${props.translate('parentReportAction.deletedTask')}`} />; + } + return ( - { - isDeletedParentAction ? ( - ${props.translate('parentReportAction.deletedTask')}`} /> - ) : ( - + - ) - } + Date: Thu, 3 Aug 2023 16:04:01 -0700 Subject: [PATCH 019/469] Update method that checks isCanceledTaskReport --- src/libs/ReportUtils.js | 15 ++++++++++++--- src/pages/home/report/ReportActionItem.js | 6 +++--- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index d1d88986e385..f2402122eda0 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -159,13 +159,22 @@ function isOpenTaskReport(report) { } /** - * Checks if the current user is assigned to the task report + * Checks if a task has been cancelled + * When a task is deleted, the parentReportAction is updated to have a isDeletedParentAction deleted flag + * This is because when you delete a task, we still allow you to chat on the report itself + * There's another situation where you don't have access to the parentReportAction (because it was created in a chat you don't have access to) + * In this case, we have added the key to the report itself * * @param {Object} report + * @param {Object} parentReportAction * @returns {Boolean} */ -function isCanceledTaskReport(report) { - return isTaskReport(report) && report.stateNum === CONST.REPORT.STATE_NUM.SUBMITTED && report.statusNum === CONST.REPORT.STATUS.CLOSED; +function isCanceledTaskReport(report, parentReportAction = {}) { + if (parentReportAction && ReportActionsUtils.isMessageDeleted(parentReportAction)) { + return true; + } + + return isTaskReport(report) && report.isDeletedParentAction; } /** diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 52ae9e688a16..de107bd139fc 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -463,9 +463,9 @@ function ReportActionItem(props) { if (ReportActionsUtils.isDeletedParentAction(parentReportAction)) { return ( ) } From 0665968fbda982bc2cc0e28edc55842326a6066e Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Thu, 3 Aug 2023 16:16:45 -0700 Subject: [PATCH 020/469] Update places we check isCancelled task report --- src/components/ReportActionItem/TaskPreview.js | 2 +- src/libs/ReportUtils.js | 10 +++++++--- src/pages/home/HeaderView.js | 4 ++-- src/pages/home/ReportScreen.js | 2 +- src/pages/home/report/ReportActionItem.js | 2 +- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/components/ReportActionItem/TaskPreview.js b/src/components/ReportActionItem/TaskPreview.js index 9179858163d8..b1babba2c220 100644 --- a/src/components/ReportActionItem/TaskPreview.js +++ b/src/components/ReportActionItem/TaskPreview.js @@ -72,7 +72,7 @@ function TaskPreview(props) { const assigneeDisplayName = lodashGet(props.personalDetailsList, [taskAssigneeAccountID, 'displayName'], ''); const taskAssignee = assigneeLogin || assigneeDisplayName; const htmlForTaskPreview = taskAssignee ? `@${taskAssignee} ${taskTitle}` : `${taskTitle}`; - const isDeletedParentAction = ReportActionsUtils.isDeletedParentAction(props.action); + const isDeletedParentAction = ReportUtils.isCanceledTaskReport(props.taskReport, props.action); if (isDeletedParentAction) { return ${props.translate('parentReportAction.deletedTask')}`} />; diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index f2402122eda0..14d7987ad09b 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -169,12 +169,16 @@ function isOpenTaskReport(report) { * @param {Object} parentReportAction * @returns {Boolean} */ -function isCanceledTaskReport(report, parentReportAction = {}) { - if (parentReportAction && ReportActionsUtils.isMessageDeleted(parentReportAction)) { +function isCanceledTaskReport(report = {}, parentReportAction = {}) { + if (!_.isEmpty(parentReportAction) && ReportActionsUtils.isMessageDeleted(parentReportAction)) { return true; } - return isTaskReport(report) && report.isDeletedParentAction; + if (!_.isEmpty(report) && report.isDeletedParentAction) { + return true; + } + + return false; } /** diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js index 0d707239bb28..afd9d98c0391 100644 --- a/src/pages/home/HeaderView.js +++ b/src/pages/home/HeaderView.js @@ -95,13 +95,13 @@ function HeaderView(props) { const isAutomatedExpensifyAccount = ReportUtils.hasSingleParticipant(props.report) && ReportUtils.hasAutomatedExpensifyAccountIDs(participants); const guideCalendarLink = lodashGet(props.account, 'guideCalendarLink'); const parentReportAction = ReportActionsUtils.getParentReportAction(props.report); - const isDeletedParentAction = ReportActionsUtils.isDeletedParentAction(parentReportAction); + const isCanceledTaskReport = ReportUtils.isCanceledTaskReport(props.report, parentReportAction); // We hide the button when we are chatting with an automated Expensify account since it's not possible to contact // these users via alternative means. It is possible to request a call with Concierge so we leave the option for them. const shouldShowCallButton = (isConcierge && guideCalendarLink) || (!isAutomatedExpensifyAccount && !isTaskReport); const threeDotMenuItems = []; - if (isTaskReport && !isDeletedParentAction) { + if (isTaskReport && !isCanceledTaskReport) { const canModifyTask = Task.canModifyTask(props.report, props.session.accountID); if (ReportUtils.isOpenTaskReport(props.report) && canModifyTask) { threeDotMenuItems.push({ diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 94fe5e2beb00..7036dc1c5c9f 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -328,7 +328,7 @@ class ReportScreen extends React.Component { needsOffscreenAlphaCompositing > {headerView} - {ReportUtils.isTaskReport(this.props.report) && this.props.isSmallScreenWidth && ReportUtils.isOpenTaskReport(this.props.report) && ( + {ReportUtils.isTaskReport(this.props.report) && this.props.isSmallScreenWidth && ReportUtils.isOpenTaskReport(this.props.report) && !ReportUtils.isCanceledTaskReport(this.props.report, parentReportAction) && ( diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index de107bd139fc..1a7c5cd65d0e 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -460,7 +460,7 @@ function ReportActionItem(props) { ); } if (ReportUtils.isTaskReport(props.report)) { - if (ReportActionsUtils.isDeletedParentAction(parentReportAction)) { + if (ReportUtils.isCanceledTaskReport(props.taskReport, parentReportAction)) { return ( Date: Thu, 3 Aug 2023 16:27:20 -0700 Subject: [PATCH 021/469] Update places isCanceledTaskReport --- src/libs/ReportUtils.js | 4 ++-- src/pages/home/HeaderView.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 14d7987ad09b..9c7d95162a5b 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -170,7 +170,7 @@ function isOpenTaskReport(report) { * @returns {Boolean} */ function isCanceledTaskReport(report = {}, parentReportAction = {}) { - if (!_.isEmpty(parentReportAction) && ReportActionsUtils.isMessageDeleted(parentReportAction)) { + if (!_.isEmpty(parentReportAction) && lodashGet(parentReportAction, ['message', 0, 'isDeletedParentAction'], false)) { return true; } @@ -1328,7 +1328,7 @@ function getReportName(report, policy = undefined) { return parentReportActionMessage || Localize.translateLocal('parentReportAction.deletedMessage'); } - if (isTaskReport(report) && ReportActionsUtils.isDeletedParentAction(parentReportAction)) { + if (isTaskReport(report) && isCanceledTaskReport(report, parentReportAction)) { return Localize.translateLocal('parentReportAction.deletedTask'); } diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js index afd9d98c0391..f7eefce127b9 100644 --- a/src/pages/home/HeaderView.js +++ b/src/pages/home/HeaderView.js @@ -218,7 +218,7 @@ function HeaderView(props) { )} - {isTaskReport && !props.isSmallScreenWidth && ReportUtils.isOpenTaskReport(props.report) && } + {isTaskReport && !props.isSmallScreenWidth && ReportUtils.isOpenTaskReport(props.report) && !isCanceledTaskReport && } {shouldShowCallButton && ( Date: Thu, 3 Aug 2023 16:27:31 -0700 Subject: [PATCH 022/469] onyx updates --- src/libs/ReportActionsUtils.js | 7 +++---- src/libs/actions/Task.js | 19 +++++++++++-------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index d6a59921dc0b..495e79558071 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -564,18 +564,17 @@ function isMessageDeleted(reportAction) { /** * When you change assignees, a task created reportAction is added to that specific assignee's report. * You can do this multiple times, so when we perform an action that modifies the parent report, we need to update all the parent report actions - * @param {*} taskReportID + * @param {*} taskReportID * @returns {Array} taskCreatedReportActions */ function getTaskCreatedReportActions(taskReportID) { const taskCreatedReportActions = []; _.each(allReports, (report) => { _.each(report.reportActions, (reportAction) => { - if (reportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT || reportAction.originalMessage.taskReportID !== taskReportID) - { + if (reportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT || reportAction.originalMessage.taskReportID !== taskReportID) { return; } - + taskCreatedReportActions.push(reportAction); }); }); diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js index 97b105e3a8ad..9d4f2e0f0417 100644 --- a/src/libs/actions/Task.js +++ b/src/libs/actions/Task.js @@ -651,14 +651,16 @@ function cancelTask(taskReportID, taskTitle, originalStateNum, originalStatusNum [parentReportAction.reportActionID]: { pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, previousMessage: parentReportAction.message, - message: [{ - translationKey: '', - type: 'COMMENT', - html: '', - text: '', - isEdited: true, - isDeletedParentAction: true, - }], + message: [ + { + translationKey: '', + type: 'COMMENT', + html: '', + text: '', + isEdited: true, + isDeletedParentAction: true, + }, + ], errors: null, linkMetaData: [], }, @@ -675,6 +677,7 @@ function cancelTask(taskReportID, taskTitle, originalStateNum, originalStatusNum lastMessageText: message, lastActorAccountID: optimisticCancelReportAction.actorAccountID, updateReportInLHN: true, + isDeletedParentAction: true, }, }, { From 84d289b9a0e19cdf3f59104089e0bc036091bd56 Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Thu, 3 Aug 2023 16:41:49 -0700 Subject: [PATCH 023/469] pass the actual report --- src/pages/home/report/ReportActionItem.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 1a7c5cd65d0e..3c2f4ce7c438 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -460,7 +460,7 @@ function ReportActionItem(props) { ); } if (ReportUtils.isTaskReport(props.report)) { - if (ReportUtils.isCanceledTaskReport(props.taskReport, parentReportAction)) { + if (ReportUtils.isCanceledTaskReport(props.report, parentReportAction)) { return ( Date: Tue, 8 Aug 2023 14:44:49 +0530 Subject: [PATCH 024/469] Fix proptype error --- src/components/AvatarWithImagePicker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/AvatarWithImagePicker.js b/src/components/AvatarWithImagePicker.js index f8ae4bcf7a3b..6aeb7c707b2d 100644 --- a/src/components/AvatarWithImagePicker.js +++ b/src/components/AvatarWithImagePicker.js @@ -84,7 +84,7 @@ const propTypes = { headerTitle: PropTypes.string, /** Avatar source for avatar preview modal */ - previewSource: PropTypes.string, + previewSource: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), /** File name of the avatar */ originalFileName: PropTypes.string, From e5c898aba838fbbe3983d51f04e8ef501de94dae Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Thu, 10 Aug 2023 16:42:08 -0700 Subject: [PATCH 025/469] Make sure we don't include cancelled tasks in the selector --- src/pages/tasks/TaskShareDestinationSelectorModal.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/tasks/TaskShareDestinationSelectorModal.js b/src/pages/tasks/TaskShareDestinationSelectorModal.js index 8272406b4cbe..61d6868ebb1e 100644 --- a/src/pages/tasks/TaskShareDestinationSelectorModal.js +++ b/src/pages/tasks/TaskShareDestinationSelectorModal.js @@ -52,7 +52,8 @@ function TaskShareDestinationSelectorModal(props) { if ( !ReportUtils.isAllowedToComment(props.reports[reportKey]) || ReportUtils.isArchivedRoom(props.reports[reportKey]) || - ReportUtils.isExpensifyOnlyParticipantInReport(props.reports[reportKey]) + ReportUtils.isExpensifyOnlyParticipantInReport(props.reports[reportKey]) || + ReportUtils.isCanceledTaskReport(props.reports[reportKey]) ) { return; } From 6d8f9f9dfdecfbea553992e87d938f86b28db002 Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Sun, 13 Aug 2023 16:04:25 +0200 Subject: [PATCH 026/469] Add overscrollContain style --- src/components/InvertedFlatList/index.js | 1 + src/styles/styles.js | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/src/components/InvertedFlatList/index.js b/src/components/InvertedFlatList/index.js index 923a17210af7..fee37a9c1514 100644 --- a/src/components/InvertedFlatList/index.js +++ b/src/components/InvertedFlatList/index.js @@ -43,6 +43,7 @@ class InvertedFlatList extends React.Component { ref={(el) => (this.list = el)} shouldMeasureItems contentContainerStyle={StyleSheet.compose(this.props.contentContainerStyle, styles.justifyContentEnd)} + style={styles.overscrollContain} /> ); } diff --git a/src/styles/styles.js b/src/styles/styles.js index 581410e0e5a5..c9de6658a84e 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -3677,6 +3677,10 @@ const styles = { rotate90: { transform: [{rotate: '90deg'}], }, + + overscrollContain: { + overscrollBehavior: 'contain', + }, }; export default styles; From 46666557d14ce2d3f32a716145d5030b5339e876 Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Tue, 15 Aug 2023 08:21:47 +0200 Subject: [PATCH 027/469] Move style to the overflow.js --- src/components/InvertedFlatList/index.js | 2 +- src/styles/styles.js | 4 ---- src/styles/utilities/overflow.js | 4 ++++ 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/InvertedFlatList/index.js b/src/components/InvertedFlatList/index.js index fee37a9c1514..505fec32ddbd 100644 --- a/src/components/InvertedFlatList/index.js +++ b/src/components/InvertedFlatList/index.js @@ -43,7 +43,7 @@ class InvertedFlatList extends React.Component { ref={(el) => (this.list = el)} shouldMeasureItems contentContainerStyle={StyleSheet.compose(this.props.contentContainerStyle, styles.justifyContentEnd)} - style={styles.overscrollContain} + style={styles.overscrollBehaviorContain} /> ); } diff --git a/src/styles/styles.js b/src/styles/styles.js index aa952358e0b7..174662ed1d29 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -3676,10 +3676,6 @@ const styles = { rotate90: { transform: [{rotate: '90deg'}], }, - - overscrollContain: { - overscrollBehavior: 'contain', - }, }; export default styles; diff --git a/src/styles/utilities/overflow.js b/src/styles/utilities/overflow.js index c190abfa912b..9b7f92cd156f 100644 --- a/src/styles/utilities/overflow.js +++ b/src/styles/utilities/overflow.js @@ -22,5 +22,9 @@ export default { overscrollBehavior: 'none', }, + overscrollBehaviorContain: { + overscrollBehavior: 'contain', + }, + overflowAuto, }; From f6d1286629e5fa8ba986c485a45994041eff3d44 Mon Sep 17 00:00:00 2001 From: Puneet Date: Thu, 17 Aug 2023 09:29:55 +0530 Subject: [PATCH 028/469] fix native attachment picker opening on clicking view photo --- src/components/AvatarWithImagePicker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/AvatarWithImagePicker.js b/src/components/AvatarWithImagePicker.js index 6aeb7c707b2d..85a55a16d595 100644 --- a/src/components/AvatarWithImagePicker.js +++ b/src/components/AvatarWithImagePicker.js @@ -320,7 +320,7 @@ class AvatarWithImagePicker extends React.Component { }, }); - menuItems.unshift({ + menuItems.push({ icon: Expensicons.Eye, text: this.props.translate('avatarWithImagePicker.viewPhoto'), onSelected: () => show(), From 61bc6ae3bcdee5484e966a70523c635da1be5736 Mon Sep 17 00:00:00 2001 From: Jack Nam Date: Thu, 17 Aug 2023 11:43:09 -0500 Subject: [PATCH 029/469] Add RenderHTML to show deleted reportAction --- src/components/ReportActionItem/TaskPreview.js | 1 - src/pages/home/report/ReportActionItem.js | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/ReportActionItem/TaskPreview.js b/src/components/ReportActionItem/TaskPreview.js index 4f2c2c7dab8b..37a53aba35c4 100644 --- a/src/components/ReportActionItem/TaskPreview.js +++ b/src/components/ReportActionItem/TaskPreview.js @@ -22,7 +22,6 @@ import * as ReportUtils from '../../libs/ReportUtils'; import RenderHTML from '../RenderHTML'; import PressableWithoutFeedback from '../Pressable/PressableWithoutFeedback'; import personalDetailsPropType from '../../pages/personalDetailsPropType'; -import * as ReportActionsUtils from '../../libs/ReportActionsUtils'; import * as Session from '../../libs/actions/Session'; const propTypes = { diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 2d2b090b75ae..9eb83e5c6648 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -66,6 +66,7 @@ import * as store from '../../../libs/actions/ReimbursementAccount/store'; import * as BankAccounts from '../../../libs/actions/BankAccounts'; import ReportScreenContext from '../ReportScreenContext'; import Permissions from '../../../libs/Permissions'; +import RenderHTML from '../../../components/RenderHTML'; const propTypes = { ...windowDimensionsPropTypes, @@ -484,7 +485,9 @@ function ReportActionItem(props) { showHeader={!props.draftMessage} wrapperStyles={[styles.chatItem]} report={props.report} - /> + > + ${props.translate('parentReportAction.deletedTask')}`} /> + ) } From d90968188ffcc7bbf765b737c07653f29e5341e0 Mon Sep 17 00:00:00 2001 From: Jakub Kosmydel <104823336+kosmydel@users.noreply.github.com> Date: Mon, 21 Aug 2023 15:49:14 +0200 Subject: [PATCH 030/469] Refactor, use platform-specific styles --- src/components/InvertedFlatList/index.js | 1 - src/pages/home/report/ReportActionsList.js | 1 + src/styles/utilities/overflow.js | 5 ++--- src/styles/utilities/overscrollBehaviorContain/index.js | 3 +++ .../utilities/overscrollBehaviorContain/index.native.js | 1 + 5 files changed, 7 insertions(+), 4 deletions(-) create mode 100644 src/styles/utilities/overscrollBehaviorContain/index.js create mode 100644 src/styles/utilities/overscrollBehaviorContain/index.native.js diff --git a/src/components/InvertedFlatList/index.js b/src/components/InvertedFlatList/index.js index 505fec32ddbd..923a17210af7 100644 --- a/src/components/InvertedFlatList/index.js +++ b/src/components/InvertedFlatList/index.js @@ -43,7 +43,6 @@ class InvertedFlatList extends React.Component { ref={(el) => (this.list = el)} shouldMeasureItems contentContainerStyle={StyleSheet.compose(this.props.contentContainerStyle, styles.justifyContentEnd)} - style={styles.overscrollBehaviorContain} /> ); } diff --git a/src/pages/home/report/ReportActionsList.js b/src/pages/home/report/ReportActionsList.js index 7f897ee825fb..4ef607e638c6 100644 --- a/src/pages/home/report/ReportActionsList.js +++ b/src/pages/home/report/ReportActionsList.js @@ -291,6 +291,7 @@ function ReportActionsList({ Date: Thu, 24 Aug 2023 10:45:41 +0200 Subject: [PATCH 031/469] Migrate index.js to function component --- src/components/InvertedFlatList/index.js | 46 +++++++++++------------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/src/components/InvertedFlatList/index.js b/src/components/InvertedFlatList/index.js index 923a17210af7..4cd15ba60521 100644 --- a/src/components/InvertedFlatList/index.js +++ b/src/components/InvertedFlatList/index.js @@ -1,4 +1,4 @@ -import React, {forwardRef} from 'react'; +import React, {forwardRef, useEffect} from 'react'; import PropTypes from 'prop-types'; import {FlatList, StyleSheet} from 'react-native'; import _ from 'underscore'; @@ -18,34 +18,30 @@ const propTypes = { // This is adapted from https://codesandbox.io/s/react-native-dsyse // It's a HACK alert since FlatList has inverted scrolling on web -class InvertedFlatList extends React.Component { - constructor(props) { - super(props); +function InvertedFlatList(props) { + const {innerRef, contentContainerStyle} = props; + let list; - this.list = undefined; - } - - componentDidMount() { - if (!_.isFunction(this.props.innerRef)) { + useEffect(() => { + if (!_.isFunction(innerRef)) { // eslint-disable-next-line no-param-reassign - this.props.innerRef.current = this.list; + innerRef.current = list; } else { - this.props.innerRef(this.list); + innerRef(list); } - } - - render() { - return ( - (this.list = el)} - shouldMeasureItems - contentContainerStyle={StyleSheet.compose(this.props.contentContainerStyle, styles.justifyContentEnd)} - /> - ); - } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + (list = el)} + shouldMeasureItems + contentContainerStyle={StyleSheet.compose(contentContainerStyle, styles.justifyContentEnd)} + /> + ); } InvertedFlatList.propTypes = propTypes; From 11473bd8d55afa9b801d8c3ab9b8825adb3af9ff Mon Sep 17 00:00:00 2001 From: AGarciaNY Date: Tue, 15 Aug 2023 21:00:20 -0400 Subject: [PATCH 032/469] converted baseErrorBoundary to function component with react-error-boundar --- package-lock.json | 20 +++++++++ package.json | 1 + .../ErrorBoundary/BaseErrorBoundary.js | 45 +++++++------------ src/pages/ErrorPage/GenericErrorPage.js | 12 ++--- 4 files changed, 44 insertions(+), 34 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1d6b5ce003ba..adfd254c4b9d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -66,6 +66,7 @@ "react-content-loader": "^6.1.0", "react-dom": "18.1.0", "react-map-gl": "^7.1.3", + "react-error-boundary": "^4.0.11", "react-native": "0.72.3", "react-native-blob-util": "^0.17.3", "react-native-collapsible": "^1.6.0", @@ -42758,6 +42759,17 @@ "react": "^18.1.0" } }, + "node_modules/react-error-boundary": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-4.0.11.tgz", + "integrity": "sha512-U13ul67aP5DOSPNSCWQ/eO0AQEYzEFkVljULQIjMV0KlffTAhxuDoBKdO0pb/JZ8mDhMKFZ9NZi0BmLGUiNphw==", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "peerDependencies": { + "react": ">=16.13.1" + } + }, "node_modules/react-freeze": { "version": "1.0.3", "license": "MIT", @@ -79709,6 +79721,14 @@ "scheduler": "^0.22.0" } }, + "react-error-boundary": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-4.0.11.tgz", + "integrity": "sha512-U13ul67aP5DOSPNSCWQ/eO0AQEYzEFkVljULQIjMV0KlffTAhxuDoBKdO0pb/JZ8mDhMKFZ9NZi0BmLGUiNphw==", + "requires": { + "@babel/runtime": "^7.12.5" + } + }, "react-freeze": { "version": "1.0.3", "requires": {} diff --git a/package.json b/package.json index eeb52419e1a0..8c99c4ff00b6 100644 --- a/package.json +++ b/package.json @@ -106,6 +106,7 @@ "react-content-loader": "^6.1.0", "react-dom": "18.1.0", "react-map-gl": "^7.1.3", + "react-error-boundary": "^4.0.11", "react-native": "0.72.3", "react-native-blob-util": "^0.17.3", "react-native-collapsible": "^1.6.0", diff --git a/src/components/ErrorBoundary/BaseErrorBoundary.js b/src/components/ErrorBoundary/BaseErrorBoundary.js index e479b04f7ade..ee058f654ea9 100644 --- a/src/components/ErrorBoundary/BaseErrorBoundary.js +++ b/src/components/ErrorBoundary/BaseErrorBoundary.js @@ -1,5 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; +import {ErrorBoundary} from 'react-error-boundary'; import BootSplash from '../../libs/BootSplash'; import GenericErrorPage from '../../pages/ErrorPage/GenericErrorPage'; @@ -22,40 +23,28 @@ const defaultProps = { * This component captures an error in the child component tree and logs it to the server * It can be used to wrap the entire app as well as to wrap specific parts for more granularity * @see {@link https://reactjs.org/docs/error-boundaries.html#where-to-place-error-boundaries} + * @param {Object=} props + * @return {null} */ -class BaseErrorBoundary extends React.Component { - constructor(props) { - super(props); - this.state = {hasError: false}; - this.clearError = this.clearError.bind(this); - } - - static getDerivedStateFromError() { - // Update state so the next render will show the fallback UI. - return {hasError: true}; - } - - componentDidCatch(error, errorInfo) { - this.props.logError(this.props.errorMessage, error, JSON.stringify(errorInfo)); - +function BaseErrorBoundary(props) { + const caughtError = (error, errorInfo) => { + props.logError(props.errorMessage, error, JSON.stringify(errorInfo)); // We hide the splash screen since the error might happened during app init BootSplash.hide(); - } - - clearError() { - this.setState({hasError: false}); - } - - render() { - if (this.state.hasError) { - return ; - } - - return this.props.children; - } + }; + + return ( + } + onError={caughtError} + > + {props.children} + + ); } BaseErrorBoundary.propTypes = propTypes; BaseErrorBoundary.defaultProps = defaultProps; +BaseErrorBoundary.displayName = 'BaseErrorBoundary'; export default BaseErrorBoundary; diff --git a/src/pages/ErrorPage/GenericErrorPage.js b/src/pages/ErrorPage/GenericErrorPage.js index 3ff3bc686419..3639b1c3de6d 100644 --- a/src/pages/ErrorPage/GenericErrorPage.js +++ b/src/pages/ErrorPage/GenericErrorPage.js @@ -1,6 +1,6 @@ import React from 'react'; -import PropTypes from 'prop-types'; import {View} from 'react-native'; +import {useErrorBoundary} from 'react-error-boundary'; import Icon from '../../components/Icon'; import defaultTheme from '../../styles/themes/default'; import * as Expensicons from '../../components/Icon/Expensicons'; @@ -19,12 +19,12 @@ import * as StyleUtils from '../../styles/StyleUtils'; const propTypes = { ...withLocalizePropTypes, - - /** Callback to call on refresh button click */ - onRefresh: PropTypes.func.isRequired, }; function GenericErrorPage(props) { + // resetBoundary request the nearest error boundary retry the render that original failed + const {resetBoundary} = useErrorBoundary(); + return ( {({paddingBottom}) => ( @@ -59,7 +59,7 @@ function GenericErrorPage(props) {