From cd283dbd8d4829dbedc54eee87ed1c8dcf0f3e5d Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Wed, 29 Nov 2023 16:13:44 +0100 Subject: [PATCH 01/21] migrate MapView to TypeScript --- src/components/MapView/index.js | 3 --- src/components/MapView/index.tsx | 10 ++++++++++ 2 files changed, 10 insertions(+), 3 deletions(-) delete mode 100644 src/components/MapView/index.js create mode 100644 src/components/MapView/index.tsx diff --git a/src/components/MapView/index.js b/src/components/MapView/index.js deleted file mode 100644 index 551f57e34ed2..000000000000 --- a/src/components/MapView/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import MapView from './MapView'; - -export default MapView; diff --git a/src/components/MapView/index.tsx b/src/components/MapView/index.tsx new file mode 100644 index 000000000000..f273845fe4c0 --- /dev/null +++ b/src/components/MapView/index.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import MapView from './MapView'; +import {ComponentProps} from './types'; + +function MapViewComponent(props: ComponentProps) { + // eslint-disable-next-line react/jsx-props-no-spreading + return ; +} + +export default MapViewComponent; From 536edac303e2b58051c2ca9fdfe9e95e53e599cd Mon Sep 17 00:00:00 2001 From: Aldo Canepa Date: Fri, 1 Dec 2023 16:58:55 -0800 Subject: [PATCH 02/21] Add validation error for category --- src/components/MoneyRequestConfirmationList.js | 5 ++++- src/languages/en.ts | 1 + src/languages/es.ts | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index 1b4967a9c54c..7fb618cdf0eb 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -448,7 +448,10 @@ function MoneyRequestConfirmationList(props) { if (_.isEmpty(selectedParticipants)) { return; } - + if (props.iouCategory && props.iouCategory.length > CONST.API_TRANSACTION_CATEGORY_MAX_LENGTH) { + setFormError('iou.error.invalidCategoryLength'); + return; + } if (props.iouType === CONST.IOU.TYPE.SEND) { if (!paymentMethod) { return; diff --git a/src/languages/en.ts b/src/languages/en.ts index 8f772f1260bb..3221b4e64326 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -595,6 +595,7 @@ export default { tagSelection: ({tagName}: TagSelectionParams) => `Select a ${tagName} to add additional organization to your money.`, categorySelection: 'Select a category to add additional organization to your money.', error: { + invalidCategoryLength: 'The length of the category chosen exceeds the maximum allowed (255). Please choose a different or shorten the category name first.', invalidAmount: 'Please enter a valid amount before continuing.', invalidSplit: 'Split amounts do not equal total amount', other: 'Unexpected error, please try again later', diff --git a/src/languages/es.ts b/src/languages/es.ts index 3887891299df..b6588fc9306d 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -589,6 +589,7 @@ export default { tagSelection: ({tagName}: TagSelectionParams) => `Seleccione una ${tagName} para organizar mejor tu dinero.`, categorySelection: 'Seleccione una categoría para organizar mejor tu dinero.', error: { + invalidCategoryLength: 'El largo de la categoría escogida excede el máximo permitido (255). Por favor escoge otra categoría o acorta la categoría primero.', invalidAmount: 'Por favor ingresa un monto válido antes de continuar.', invalidSplit: 'La suma de las partes no equivale al monto total', other: 'Error inesperado, por favor inténtalo más tarde', From 10f4332272d3953df8c773177640e7c1dd2743d3 Mon Sep 17 00:00:00 2001 From: Aldo Canepa Date: Mon, 4 Dec 2023 12:44:53 -0800 Subject: [PATCH 03/21] Add Max category length const --- src/CONST.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/CONST.ts b/src/CONST.ts index 13b79179f431..b2be0d05d985 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -55,6 +55,9 @@ const CONST = { ALLOWED_RECEIPT_EXTENSIONS: ['jpg', 'jpeg', 'gif', 'png', 'pdf', 'htm', 'html', 'text', 'rtf', 'doc', 'tif', 'tiff', 'msword', 'zip', 'xml', 'message'], }, + // This is limit set on servers, do not update without wider internal discussion + API_TRANSACTION_CATEGORY_MAX_LENGTH: 255, + AUTO_AUTH_STATE: { NOT_STARTED: 'not-started', SIGNING_IN: 'signing-in', From 1243b91222a0b4648fd9373b321384bc68e9049b Mon Sep 17 00:00:00 2001 From: Aldo Canepa Date: Mon, 4 Dec 2023 16:26:43 -0800 Subject: [PATCH 04/21] Add useCallback dependency --- src/components/MoneyRequestConfirmationList.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index 7fb618cdf0eb..34c4e7100e9a 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -486,6 +486,7 @@ function MoneyRequestConfirmationList(props) { props.isEditingSplitBill, props.iouType, props.isDistanceRequest, + props.iouCategory, isDistanceRequestWithoutRoute, props.iouCurrencyCode, props.iouAmount, From 3caca067b2c2063677045ee8e470db0222ca6014 Mon Sep 17 00:00:00 2001 From: maddylewis <38016013+maddylewis@users.noreply.github.com> Date: Tue, 5 Dec 2023 11:41:06 -0500 Subject: [PATCH 05/21] Delete docs/articles/expensify-classic/getting-started/Mobile-App.md The resource was incorrectly published by creating a new file (Using The Mobile App). I will delete this file since it's no longer needed. --- .../articles/expensify-classic/getting-started/Mobile-App.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 docs/articles/expensify-classic/getting-started/Mobile-App.md diff --git a/docs/articles/expensify-classic/getting-started/Mobile-App.md b/docs/articles/expensify-classic/getting-started/Mobile-App.md deleted file mode 100644 index 7fa57abbdf61..000000000000 --- a/docs/articles/expensify-classic/getting-started/Mobile-App.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Using the App -description: Using the App ---- -## Resource Coming Soon! From 2b8b7986c2a410b2eea22640dd4901737bfd1ef3 Mon Sep 17 00:00:00 2001 From: maddylewis <38016013+maddylewis@users.noreply.github.com> Date: Tue, 5 Dec 2023 11:44:44 -0500 Subject: [PATCH 06/21] Delete docs/articles/expensify-classic/bank-accounts-and-credit-cards/Personal-Cards.md the article was incorrectly published via a new file (personal credit cards). deleting this file since it's no longer needed. --- .../bank-accounts-and-credit-cards/Personal-Cards.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 docs/articles/expensify-classic/bank-accounts-and-credit-cards/Personal-Cards.md diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Personal-Cards.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Personal-Cards.md deleted file mode 100644 index 71edcdeba00d..000000000000 --- a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Personal-Cards.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Personal Cards -description: Connect your credit card directly to Expensify to easily track your personal finances. ---- -## Resource Coming Soon! From fc4f4914bad9953f36d5affa06abd0a1a30a47bc Mon Sep 17 00:00:00 2001 From: maddylewis <38016013+maddylewis@users.noreply.github.com> Date: Tue, 5 Dec 2023 11:47:01 -0500 Subject: [PATCH 07/21] Delete docs/articles/expensify-classic/account-settings/Profile-Settings.md i believe this subcategory is no longer needed - deleting --- .../expensify-classic/account-settings/Profile-Settings.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 docs/articles/expensify-classic/account-settings/Profile-Settings.md diff --git a/docs/articles/expensify-classic/account-settings/Profile-Settings.md b/docs/articles/expensify-classic/account-settings/Profile-Settings.md deleted file mode 100644 index 3b2a0b830926..000000000000 --- a/docs/articles/expensify-classic/account-settings/Profile-Settings.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Profile Settings -description: Profile Settings ---- -## Resource Coming Soon! From 77549568cf0e596ab0113c42e3a60b5cf9b4b4f0 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Thu, 7 Dec 2023 17:29:44 +0700 Subject: [PATCH 08/21] fix the current issue and regression --- src/components/AttachmentModal.js | 27 +++++++++------- .../extractAttachmentsFromReport.js | 29 ++--------------- .../Attachments/AttachmentCarousel/index.js | 27 ++-------------- .../AttachmentCarousel/index.native.js | 26 ++-------------- .../ReportActionItem/ReportActionItemImage.js | 31 ++++++++++++------- 5 files changed, 42 insertions(+), 98 deletions(-) diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index 57b0c6466a7f..5bfc607c0480 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -90,6 +90,9 @@ const propTypes = { /** Denotes whether it is a workspace avatar or not */ isWorkspaceAvatar: PropTypes.bool, + + /** Whether it is a receipt attachment or not */ + isReceiptAttachment: PropTypes.bool, }; const defaultProps = { @@ -107,6 +110,7 @@ const defaultProps = { onModalHide: () => {}, onCarouselAttachmentChange: () => {}, isWorkspaceAvatar: false, + isReceiptAttachment: false, }; function AttachmentModal(props) { @@ -118,7 +122,6 @@ function AttachmentModal(props) { const [isAttachmentInvalid, setIsAttachmentInvalid] = useState(false); const [isDeleteReceiptConfirmModalVisible, setIsDeleteReceiptConfirmModalVisible] = useState(false); const [isAuthTokenRequired, setIsAuthTokenRequired] = useState(props.isAuthTokenRequired); - const [isAttachmentReceipt, setIsAttachmentReceipt] = useState(null); const [attachmentInvalidReasonTitle, setAttachmentInvalidReasonTitle] = useState(''); const [attachmentInvalidReason, setAttachmentInvalidReason] = useState(null); const [source, setSource] = useState(props.source); @@ -154,7 +157,6 @@ function AttachmentModal(props) { (attachment) => { setSource(attachment.source); setFile(attachment.file); - setIsAttachmentReceipt(attachment.isReceipt); setIsAuthTokenRequired(attachment.isAuthTokenRequired); onCarouselAttachmentChange(attachment); }, @@ -357,7 +359,7 @@ function AttachmentModal(props) { const sourceForAttachmentView = props.source || source; const threeDotsMenuItems = useMemo(() => { - if (!isAttachmentReceipt || !props.parentReport || !props.parentReportActions) { + if (!props.isReceiptAttachment || !props.parentReport || !props.parentReportActions) { return []; } const menuItems = []; @@ -392,17 +394,17 @@ function AttachmentModal(props) { } return menuItems; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isAttachmentReceipt, props.parentReport, props.parentReportActions, props.policy, props.transaction, file]); + }, [props.isReceiptAttachment, props.parentReport, props.parentReportActions, props.policy, props.transaction, file]); // There are a few things that shouldn't be set until we absolutely know if the file is a receipt or an attachment. - // isAttachmentReceipt will be null until its certain what the file is, in which case it will then be true|false. + // props.isReceiptAttachment will be null until its certain what the file is, in which case it will then be true|false. let headerTitle = props.headerTitle; let shouldShowDownloadButton = false; let shouldShowThreeDotsButton = false; - if (!_.isNull(isAttachmentReceipt)) { - headerTitle = translate(isAttachmentReceipt ? 'common.receipt' : 'common.attachment'); - shouldShowDownloadButton = props.allowDownload && isDownloadButtonReadyToBeShown && !isAttachmentReceipt && !isOffline; - shouldShowThreeDotsButton = isAttachmentReceipt && isModalOpen; + if (!_.isEmpty(props.report)) { + headerTitle = translate(props.isReceiptAttachment ? 'common.receipt' : 'common.attachment'); + shouldShowDownloadButton = props.allowDownload && isDownloadButtonReadyToBeShown && !props.isReceiptAttachment && !isOffline; + shouldShowThreeDotsButton = props.isReceiptAttachment && isModalOpen; } return ( @@ -443,7 +445,7 @@ function AttachmentModal(props) { shouldOverlay /> - {!_.isEmpty(props.report) ? ( + {!_.isEmpty(props.report) && !props.isReceiptAttachment ? ( ) )} @@ -486,7 +489,7 @@ function AttachmentModal(props) { )} )} - {isAttachmentReceipt && ( + {props.isReceiptAttachment && ( )} - {!isAttachmentReceipt && ( + {!props.isReceiptAttachment && ( { - if (!ReportActionsUtils.shouldReportActionBeVisible(action, key)) { + if (!ReportActionsUtils.shouldReportActionBeVisible(action, key) || ReportActionsUtils.isMoneyRequestAction(action)) { return; } - // We're handling receipts differently here because receipt images are not - // part of the report action message, the images are constructed client-side - if (ReportActionsUtils.isMoneyRequestAction(action)) { - const transactionID = lodashGet(action, ['originalMessage', 'IOUTransactionID']); - if (!transactionID) { - return; - } - - if (TransactionUtils.hasReceipt(transaction)) { - const {image} = ReceiptUtils.getThumbnailAndImageURIs(transaction); - const isLocalFile = typeof image === 'string' && _.some(CONST.ATTACHMENT_LOCAL_URL_PREFIX, (prefix) => image.startsWith(prefix)); - attachments.unshift({ - source: tryResolveUrlFromApiRoot(image), - isAuthTokenRequired: !isLocalFile, - file: {name: transaction.filename}, - isReceipt: true, - transactionID, - }); - return; - } - } - const decision = _.get(action, ['message', 0, 'moderationDecision', 'decision'], ''); const hasBeenFlagged = decision === CONST.MODERATION.MODERATOR_DECISION_PENDING_HIDE || decision === CONST.MODERATION.MODERATOR_DECISION_HIDDEN; const html = _.get(action, ['message', 0, 'html'], '').replace('/>', `data-flagged="${hasBeenFlagged}" data-id="${action.reportActionID}"/>`); diff --git a/src/components/Attachments/AttachmentCarousel/index.js b/src/components/Attachments/AttachmentCarousel/index.js index 141e619e489e..1696f4adf0b4 100644 --- a/src/components/Attachments/AttachmentCarousel/index.js +++ b/src/components/Attachments/AttachmentCarousel/index.js @@ -1,4 +1,3 @@ -import lodashGet from 'lodash/get'; import React, {useCallback, useEffect, useRef, useState} from 'react'; import {FlatList, Keyboard, PixelRatio, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; @@ -28,7 +27,7 @@ const viewabilityConfig = { itemVisiblePercentThreshold: 95, }; -function AttachmentCarousel({report, reportActions, parentReportActions, source, onNavigate, setDownloadButtonVisibility, translate, transaction}) { +function AttachmentCarousel({report, reportActions, parentReportActions, source, onNavigate, setDownloadButtonVisibility, translate}) { const styles = useThemeStyles(); const scrollRef = useRef(null); @@ -39,21 +38,12 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source, const [attachments, setAttachments] = useState([]); const [activeSource, setActiveSource] = useState(source); const [shouldShowArrows, setShouldShowArrows, autoHideArrows, cancelAutoHideArrows] = useCarouselArrows(); - const [isReceipt, setIsReceipt] = useState(false); - const compareImage = useCallback( - (attachment) => { - if (attachment.isReceipt && isReceipt) { - return attachment.transactionID === transaction.transactionID; - } - return attachment.source === source; - }, - [source, isReceipt, transaction], - ); + const compareImage = useCallback((attachment) => attachment.source === source, [source]); useEffect(() => { const parentReportAction = parentReportActions[report.parentReportActionID]; - const attachmentsFromReport = extractAttachmentsFromReport(parentReportAction, reportActions, transaction); + const attachmentsFromReport = extractAttachmentsFromReport(parentReportAction, reportActions); const initialPage = _.findIndex(attachmentsFromReport, compareImage); @@ -88,12 +78,10 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source, // to get the index of the current page const entry = _.first(viewableItems); if (!entry) { - setIsReceipt(false); setActiveSource(null); return; } - setIsReceipt(entry.item.isReceipt); setPage(entry.index); setActiveSource(entry.item.source); @@ -241,15 +229,6 @@ export default compose( canEvict: false, }, }), - // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file - withOnyx({ - transaction: { - key: ({report, parentReportActions}) => { - const parentReportAction = lodashGet(parentReportActions, [report.parentReportActionID]); - return `${ONYXKEYS.COLLECTION.TRANSACTION}${lodashGet(parentReportAction, 'originalMessage.IOUTransactionID', 0)}`; - }, - }, - }), withLocalize, withWindowDimensions, )(AttachmentCarousel); diff --git a/src/components/Attachments/AttachmentCarousel/index.native.js b/src/components/Attachments/AttachmentCarousel/index.native.js index 6bf4e63c01e7..d3f70531cf28 100644 --- a/src/components/Attachments/AttachmentCarousel/index.native.js +++ b/src/components/Attachments/AttachmentCarousel/index.native.js @@ -1,4 +1,3 @@ -import lodashGet from 'lodash/get'; import React, {useCallback, useEffect, useRef, useState} from 'react'; import {Keyboard, PixelRatio, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; @@ -18,7 +17,7 @@ import extractAttachmentsFromReport from './extractAttachmentsFromReport'; import AttachmentCarouselPager from './Pager'; import useCarouselArrows from './useCarouselArrows'; -function AttachmentCarousel({report, reportActions, parentReportActions, source, onNavigate, setDownloadButtonVisibility, translate, transaction, onClose}) { +function AttachmentCarousel({report, reportActions, parentReportActions, source, onNavigate, setDownloadButtonVisibility, translate, onClose}) { const styles = useThemeStyles(); const pagerRef = useRef(null); @@ -28,21 +27,12 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source, const [activeSource, setActiveSource] = useState(source); const [isPinchGestureRunning, setIsPinchGestureRunning] = useState(true); const [shouldShowArrows, setShouldShowArrows, autoHideArrows, cancelAutoHideArrows] = useCarouselArrows(); - const [isReceipt, setIsReceipt] = useState(false); - const compareImage = useCallback( - (attachment) => { - if (attachment.isReceipt && isReceipt) { - return attachment.transactionID === transaction.transactionID; - } - return attachment.source === source; - }, - [source, isReceipt, transaction], - ); + const compareImage = useCallback((attachment) => attachment.source === source, [source]); useEffect(() => { const parentReportAction = parentReportActions[report.parentReportActionID]; - const attachmentsFromReport = extractAttachmentsFromReport(parentReportAction, reportActions, transaction); + const attachmentsFromReport = extractAttachmentsFromReport(parentReportAction, reportActions); const initialPage = _.findIndex(attachmentsFromReport, compareImage); @@ -77,7 +67,6 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source, const item = attachments[newPageIndex]; setPage(newPageIndex); - setIsReceipt(item.isReceipt); setActiveSource(item.source); onNavigate(item); @@ -186,14 +175,5 @@ export default compose( canEvict: false, }, }), - // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file - withOnyx({ - transaction: { - key: ({report, parentReportActions}) => { - const parentReportAction = lodashGet(parentReportActions, [report.parentReportActionID]); - return `${ONYXKEYS.COLLECTION.TRANSACTION}${lodashGet(parentReportAction, 'originalMessage.IOUTransactionID', 0)}`; - }, - }, - }), withLocalize, )(AttachmentCarousel); diff --git a/src/components/ReportActionItem/ReportActionItemImage.js b/src/components/ReportActionItem/ReportActionItemImage.js index f0eed3ac2f02..6fd322a24d3c 100644 --- a/src/components/ReportActionItem/ReportActionItemImage.js +++ b/src/components/ReportActionItem/ReportActionItemImage.js @@ -2,6 +2,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import {View} from 'react-native'; import _ from 'underscore'; +import AttachmentModal from '@components/AttachmentModal'; import EReceiptThumbnail from '@components/EReceiptThumbnail'; import Image from '@components/Image'; import PressableWithoutFocus from '@components/Pressable/PressableWithoutFocus'; @@ -9,12 +10,10 @@ import {ShowContextMenuContext} from '@components/ShowContextMenuContext'; import ThumbnailImage from '@components/ThumbnailImage'; import transactionPropTypes from '@components/transactionPropTypes'; import useLocalize from '@hooks/useLocalize'; -import Navigation from '@libs/Navigation/Navigation'; import * as TransactionUtils from '@libs/TransactionUtils'; import tryResolveUrlFromApiRoot from '@libs/tryResolveUrlFromApiRoot'; import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; -import ROUTES from '@src/ROUTES'; const propTypes = { /** thumbnail URI for the image */ @@ -83,17 +82,25 @@ function ReportActionItemImage({thumbnail, image, enablePreviewModal, transactio return ( {({report}) => ( - { - const route = ROUTES.REPORT_ATTACHMENTS.getRoute(report.reportID, imageSource); - Navigation.navigate(route); - }} - role={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} - accessibilityLabel={translate('accessibilityHints.viewAttachment')} + - {receiptImageComponent} - + {({show}) => ( + + {receiptImageComponent} + + )} + )} ); From 0e7b5cfa375f2be3f95329909d9c6d396e53c62d Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Thu, 7 Dec 2023 18:01:43 +0700 Subject: [PATCH 09/21] remove call back --- src/components/AttachmentModal.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index 5bfc607c0480..6e47657eb593 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -116,7 +116,6 @@ const defaultProps = { function AttachmentModal(props) { const theme = useTheme(); const styles = useThemeStyles(); - const onModalHideCallbackRef = useRef(null); const [isModalOpen, setIsModalOpen] = useState(props.defaultOpen); const [shouldLoadAttachment, setShouldLoadAttachment] = useState(false); const [isAttachmentInvalid, setIsAttachmentInvalid] = useState(false); @@ -373,7 +372,7 @@ function AttachmentModal(props) { icon: Expensicons.Camera, text: props.translate('common.replace'), onSelected: () => { - onModalHideCallbackRef.current = () => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(props.report.reportID, CONST.EDIT_REQUEST_FIELD.RECEIPT)); + Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(props.report.reportID, CONST.EDIT_REQUEST_FIELD.RECEIPT)); closeModal(); }, }); @@ -421,10 +420,6 @@ function AttachmentModal(props) { }} onModalHide={(e) => { props.onModalHide(e); - if (onModalHideCallbackRef.current) { - onModalHideCallbackRef.current(); - } - setShouldLoadAttachment(false); }} propagateSwipe From 8ec3cb58847c6d6bc01a03c9598caab34ec9423b Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Thu, 7 Dec 2023 18:37:39 +0700 Subject: [PATCH 10/21] fix lint --- src/components/AttachmentModal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index 6e47657eb593..f8396a1b47ff 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -2,7 +2,7 @@ import Str from 'expensify-common/lib/str'; import lodashExtend from 'lodash/extend'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; +import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {Animated, Keyboard, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; From ebe070fd508c1041ea678f907b266ebebbb9a379 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sat, 9 Dec 2023 11:09:22 +0800 Subject: [PATCH 11/21] replace translate with margin --- .../Navigation/AppNavigator/getRootNavigatorScreenOptions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/getRootNavigatorScreenOptions.ts b/src/libs/Navigation/AppNavigator/getRootNavigatorScreenOptions.ts index 9af92dd3e019..b294e6d7ab11 100644 --- a/src/libs/Navigation/AppNavigator/getRootNavigatorScreenOptions.ts +++ b/src/libs/Navigation/AppNavigator/getRootNavigatorScreenOptions.ts @@ -42,8 +42,8 @@ export default (isSmallScreenWidth: boolean, themeStyles: ThemeStyles): ScreenOp ...getNavigationModalCardStyle(), width: isSmallScreenWidth ? '100%' : variables.sideBarWidth, - // We need to translate the sidebar to not be covered by the StackNavigator so it can be clickable. - transform: [{translateX: isSmallScreenWidth ? 0 : -variables.sideBarWidth}], + // We need to shift the sidebar to not be covered by the StackNavigator so it can be clickable. + marginLeft: isSmallScreenWidth ? 0 : -variables.sideBarWidth, ...(isSmallScreenWidth ? {} : themeStyles.borderRight), }, }, From 1e3d9bcb88228a29307b588004d04e67c1ae5a25 Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Mon, 11 Dec 2023 15:56:33 +0700 Subject: [PATCH 12/21] fix referral button is not focused --- src/components/CopyTextToClipboard.js | 3 +++ src/components/Pressable/PressableWithDelayToggle.tsx | 3 +++ src/pages/ReferralDetailsPage.js | 1 + 3 files changed, 7 insertions(+) diff --git a/src/components/CopyTextToClipboard.js b/src/components/CopyTextToClipboard.js index acd3f08f2b22..678537c6a3d7 100644 --- a/src/components/CopyTextToClipboard.js +++ b/src/components/CopyTextToClipboard.js @@ -13,12 +13,14 @@ const propTypes = { // eslint-disable-next-line react/forbid-prop-types textStyles: PropTypes.arrayOf(PropTypes.object), urlToCopy: PropTypes.string, + accessibilityRole: PropTypes.string, ...withLocalizePropTypes, }; const defaultProps = { textStyles: [], urlToCopy: null, + accessibilityRole: undefined, }; function CopyTextToClipboard(props) { @@ -34,6 +36,7 @@ function CopyTextToClipboard(props) { icon={Expensicons.Copy} textStyles={props.textStyles} onPress={copyToClipboard} + accessibilityRole={props.accessibilityRole} /> ); } diff --git a/src/components/Pressable/PressableWithDelayToggle.tsx b/src/components/Pressable/PressableWithDelayToggle.tsx index b0abe0a91c6c..5e072dc6b1a1 100644 --- a/src/components/Pressable/PressableWithDelayToggle.tsx +++ b/src/components/Pressable/PressableWithDelayToggle.tsx @@ -48,6 +48,7 @@ type PressableWithDelayToggleProps = PressableProps & { * vertical text alignment of non-Text elements */ inline?: boolean; + accessibilityRole?: string; }; function PressableWithDelayToggle( @@ -63,6 +64,7 @@ function PressableWithDelayToggle( textStyles, iconStyles, icon, + accessibilityRole, }: PressableWithDelayToggleProps, ref: PressableRef, ) { @@ -101,6 +103,7 @@ function PressableWithDelayToggle( onPress={updatePressState} accessibilityLabel={tooltipTexts} suppressHighlighting={inline ? true : undefined} + accessibilityRole={accessibilityRole} > <> {inline && labelText} diff --git a/src/pages/ReferralDetailsPage.js b/src/pages/ReferralDetailsPage.js index 60b5d23b39da..b4d2b5e3bb9c 100644 --- a/src/pages/ReferralDetailsPage.js +++ b/src/pages/ReferralDetailsPage.js @@ -90,6 +90,7 @@ function ReferralDetailsPage({route, account}) { {shouldShowClipboard && ( Date: Mon, 11 Dec 2023 13:47:36 +0100 Subject: [PATCH 13/21] add budget policy changelogs actions --- src/CONST.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/CONST.ts b/src/CONST.ts index f1364ebbb5bf..91d55c8d6dcd 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -506,6 +506,7 @@ const CONST = { TASKREOPENED: 'TASKREOPENED', POLICYCHANGELOG: { ADD_APPROVER_RULE: 'POLICYCHANGELOG_ADD_APPROVER_RULE', + ADD_BUDGET: 'POLICYCHANGELOG_ADD_BUDGET', ADD_CATEGORY: 'POLICYCHANGELOG_ADD_CATEGORY', ADD_CUSTOM_UNIT: 'POLICYCHANGELOG_ADD_CUSTOM_UNIT', ADD_CUSTOM_UNIT_RATE: 'POLICYCHANGELOG_ADD_CUSTOM_UNIT_RATE', @@ -515,6 +516,7 @@ const CONST = { ADD_TAG: 'POLICYCHANGELOG_ADD_TAG', DELETE_ALL_TAGS: 'POLICYCHANGELOG_DELETE_ALL_TAGS', DELETE_APPROVER_RULE: 'POLICYCHANGELOG_DELETE_APPROVER_RULE', + DELETE_BUDGET: 'POLICYCHANGELOG_DELETE_BUDGET', DELETE_CATEGORY: 'POLICYCHANGELOG_DELETE_CATEGORY', DELETE_CUSTOM_UNIT: 'POLICYCHANGELOG_DELETE_CUSTOM_UNIT', DELETE_CUSTOM_UNIT_RATE: 'POLICYCHANGELOG_DELETE_CUSTOM_UNIT_RATE', @@ -536,6 +538,7 @@ const CONST = { UPDATE_AUTOHARVESTING: 'POLICYCHANGELOG_UPDATE_AUTOHARVESTING', UPDATE_AUTOREIMBURSEMENT: 'POLICYCHANGELOG_UPDATE_AUTOREIMBURSEMENT', UPDATE_AUTOREPORTING_FREQUENCY: 'POLICYCHANGELOG_UPDATE_AUTOREPORTING_FREQUENCY', + UPDATE_BUDGET: 'POLICYCHANGELOG_UPDATE_BUDGET', UPDATE_CATEGORY: 'POLICYCHANGELOG_UPDATE_CATEGORY', UPDATE_CURRENCY: 'POLICYCHANGELOG_UPDATE_CURRENCY', UPDATE_CUSTOM_UNIT: 'POLICYCHANGELOG_UPDATE_CUSTOM_UNIT', From e7ef4bd2923f557698f3dbad3b53b69a274873f4 Mon Sep 17 00:00:00 2001 From: Kevin Brian Bader Date: Mon, 11 Dec 2023 15:31:16 +0200 Subject: [PATCH 14/21] IOU - Back button unresponsive on touches on IOU details page in offline --- src/components/ReportHeaderSkeletonView.tsx | 5 +++-- src/pages/home/HeaderView.js | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/ReportHeaderSkeletonView.tsx b/src/components/ReportHeaderSkeletonView.tsx index acc9261889bc..8f13fcbf7a1b 100644 --- a/src/components/ReportHeaderSkeletonView.tsx +++ b/src/components/ReportHeaderSkeletonView.tsx @@ -14,9 +14,10 @@ import SkeletonViewContentLoader from './SkeletonViewContentLoader'; type ReportHeaderSkeletonViewProps = { shouldAnimate?: boolean; + onBackButtonPress?: () => void; }; -function ReportHeaderSkeletonView({shouldAnimate = true}: ReportHeaderSkeletonViewProps) { +function ReportHeaderSkeletonView({shouldAnimate = true, onBackButtonPress = () => {}}: ReportHeaderSkeletonViewProps) { const theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -27,7 +28,7 @@ function ReportHeaderSkeletonView({shouldAnimate = true}: ReportHeaderSkeletonVi {isSmallScreenWidth && ( {}} + onPress={onBackButtonPress} style={[styles.LHNToggle]} role={CONST.ACCESSIBILITY_ROLE.BUTTON} accessibilityLabel={translate('common.back')} diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js index 5b57419c8530..04aef6371074 100644 --- a/src/pages/home/HeaderView.js +++ b/src/pages/home/HeaderView.js @@ -197,7 +197,7 @@ function HeaderView(props) { > {isLoading ? ( - + ) : ( <> {isSmallScreenWidth && ( From bed83190c2015b02e2d76350cf1fd053351a8ce8 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 11 Dec 2023 20:33:19 +0700 Subject: [PATCH 15/21] remove comment --- src/components/Attachments/AttachmentCarousel/index.js | 1 - src/components/Attachments/AttachmentCarousel/index.native.js | 1 - 2 files changed, 2 deletions(-) diff --git a/src/components/Attachments/AttachmentCarousel/index.js b/src/components/Attachments/AttachmentCarousel/index.js index 1696f4adf0b4..fb31e32de91c 100644 --- a/src/components/Attachments/AttachmentCarousel/index.js +++ b/src/components/Attachments/AttachmentCarousel/index.js @@ -215,7 +215,6 @@ AttachmentCarousel.defaultProps = defaultProps; AttachmentCarousel.displayName = 'AttachmentCarousel'; export default compose( - // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file withOnyx({ reportActions: { key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, diff --git a/src/components/Attachments/AttachmentCarousel/index.native.js b/src/components/Attachments/AttachmentCarousel/index.native.js index d3f70531cf28..ea45509d6ce3 100644 --- a/src/components/Attachments/AttachmentCarousel/index.native.js +++ b/src/components/Attachments/AttachmentCarousel/index.native.js @@ -161,7 +161,6 @@ AttachmentCarousel.defaultProps = defaultProps; AttachmentCarousel.displayName = 'AttachmentCarousel'; export default compose( - // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file withOnyx({ reportActions: { key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, From e963f05b37b681454c547e5a4c538e5e9b76e708 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 11 Dec 2023 20:39:29 +0700 Subject: [PATCH 16/21] pass transactionID to AttachmentView --- src/components/AttachmentModal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index e0646f39a148..79be536945ac 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -462,7 +462,7 @@ function AttachmentModal(props) { isWorkspaceAvatar={props.isWorkspaceAvatar} fallbackSource={props.fallbackSource} isUsedInAttachmentModal - transaction={props.transaction} + transactionID={props.transaction.transactionID} /> ) )} From 1fd45bde731334a2e5b3e534dbdeefee67e54828 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Mon, 11 Dec 2023 23:35:24 +0800 Subject: [PATCH 17/21] fall back to transaction object --- src/pages/iou/SplitBillDetailsPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/SplitBillDetailsPage.js b/src/pages/iou/SplitBillDetailsPage.js index 65bf43500f88..dd9e7ce93cc6 100644 --- a/src/pages/iou/SplitBillDetailsPage.js +++ b/src/pages/iou/SplitBillDetailsPage.js @@ -143,7 +143,7 @@ function SplitBillDetailsPage(props) { hasSmartScanFailed={hasSmartScanFailed} reportID={reportID} reportActionID={reportAction.reportActionID} - transaction={isEditingSplitBill ? props.draftTransaction : props.transaction} + transaction={isEditingSplitBill ? props.draftTransaction || props.transaction : props.transaction} onConfirm={onConfirm} isPolicyExpenseChat={ReportUtils.isPolicyExpenseChat(props.report)} policyID={ReportUtils.isPolicyExpenseChat(props.report) && props.report.policyID} From 8d737cb52e66e17c01cff63261a06c8c335250e9 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Mon, 11 Dec 2023 11:03:10 -0700 Subject: [PATCH 18/21] use state for selected participants --- ...yTemporaryForRefactorRequestParticipantsSelector.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index 165852062c54..15f0926a15f8 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -97,6 +97,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ personalDetails: [], userToInvite: null, }); + const [selectedOptions, setSelectedOptions] = useState(participants); const {isOffline} = useNetwork(); const maxParticipantsReached = participants.length === CONST.REPORT.MAXIMUM_PARTICIPANTS; @@ -112,7 +113,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ const formatResults = OptionsListUtils.formatSectionsFromSearchTerm( searchTerm, - participants, + selectedOptions, newChatOptions.recentReports, newChatOptions.personalDetails, personalDetails, @@ -155,7 +156,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ } return newSections; - }, [maxParticipantsReached, newChatOptions, participants, personalDetails, translate, searchTerm]); + }, [maxParticipantsReached, newChatOptions, selectedOptions, personalDetails, translate, searchTerm]); /** * Adds a single participant to the request @@ -207,7 +208,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ }, ]; } - + setSelectedOptions(newSelectedOptions); onParticipantsAdded(newSelectedOptions); const chatOptions = OptionsListUtils.getFilteredOptions( @@ -268,6 +269,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ true, true, ); + setSelectedOptions(participants); setNewChatOptions({ recentReports: chatOptions.recentReports, personalDetails: chatOptions.personalDetails, @@ -325,7 +327,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ multipleOptionSelectorButtonText={translate('iou.split')} onAddToSelection={addParticipantToSelection} sections={sections} - selectedOptions={participants} + selectedOptions={selectedOptions} value={searchTerm} onSelectRow={addSingleParticipant} onChangeText={setSearchTermAndSearchInServer} From daf7cbee96c27eff64c49a6ac0dbc852766ffb24 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Mon, 11 Dec 2023 11:10:15 -0700 Subject: [PATCH 19/21] rm unecessary chatOptions computation --- ...yForRefactorRequestParticipantsSelector.js | 34 +++---------------- 1 file changed, 5 insertions(+), 29 deletions(-) diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index 15f0926a15f8..fcc524b9b990 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -97,7 +97,6 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ personalDetails: [], userToInvite: null, }); - const [selectedOptions, setSelectedOptions] = useState(participants); const {isOffline} = useNetwork(); const maxParticipantsReached = participants.length === CONST.REPORT.MAXIMUM_PARTICIPANTS; @@ -113,7 +112,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ const formatResults = OptionsListUtils.formatSectionsFromSearchTerm( searchTerm, - selectedOptions, + participants, newChatOptions.recentReports, newChatOptions.personalDetails, personalDetails, @@ -156,7 +155,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ } return newSections; - }, [maxParticipantsReached, newChatOptions, selectedOptions, personalDetails, translate, searchTerm]); + }, [maxParticipantsReached, newChatOptions, participants, personalDetails, translate, searchTerm]); /** * Adds a single participant to the request @@ -208,32 +207,10 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ }, ]; } - setSelectedOptions(newSelectedOptions); - onParticipantsAdded(newSelectedOptions); - const chatOptions = OptionsListUtils.getFilteredOptions( - reports, - personalDetails, - betas, - isOptionInList ? searchTerm : '', - newSelectedOptions, - CONST.EXPENSIFY_EMAILS, - - // If we are using this component in the "Request money" flow then we pass the includeOwnedWorkspaceChats argument so that the current user - // sees the option to request money from their admin on their own Workspace Chat. - iouType === CONST.IOU.TYPE.REQUEST, - - // We don't want to include any P2P options like personal details or reports that are not workspace chats for certain features. - iouType !== CONST.IOU.REQUEST_TYPE.DISTANCE, - ); - - setNewChatOptions({ - recentReports: chatOptions.recentReports, - personalDetails: chatOptions.personalDetails, - userToInvite: chatOptions.userToInvite, - }); + onParticipantsAdded(newSelectedOptions); }, - [participants, onParticipantsAdded, reports, personalDetails, betas, searchTerm, iouType], + [participants, onParticipantsAdded], ); const headerMessage = OptionsListUtils.getHeaderMessage( @@ -269,7 +246,6 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ true, true, ); - setSelectedOptions(participants); setNewChatOptions({ recentReports: chatOptions.recentReports, personalDetails: chatOptions.personalDetails, @@ -327,7 +303,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ multipleOptionSelectorButtonText={translate('iou.split')} onAddToSelection={addParticipantToSelection} sections={sections} - selectedOptions={selectedOptions} + selectedOptions={participants} value={searchTerm} onSelectRow={addSingleParticipant} onChangeText={setSearchTermAndSearchInServer} From 9d1f3b8760147459d445212b2263c94599ff3bbe Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Mon, 11 Dec 2023 11:16:36 -0700 Subject: [PATCH 20/21] do not invite users for distance requests --- .../MoneyTemporaryForRefactorRequestParticipantsSelector.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index fcc524b9b990..95fa64723737 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -243,7 +243,10 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ false, {}, [], - true, + + // We don't want the user to be able to invite individuals when they are in the "Distance request" flow for now. + // This functionality is being built here: https://github.com/Expensify/App/issues/23291 + iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE, true, ); setNewChatOptions({ From 458f1213199d3668f16bfa8b42ef329ad80a0ec5 Mon Sep 17 00:00:00 2001 From: OSBotify Date: Mon, 11 Dec 2023 20:07:28 +0000 Subject: [PATCH 21/21] Update version to 1.4.11-2 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index e2145d8f2848..20e6ea43bac7 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -91,8 +91,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001041101 - versionName "1.4.11-1" + versionCode 1001041102 + versionName "1.4.11-2" } flavorDimensions "default" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 38df19731ec6..6b56a92d222e 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.11.1 + 1.4.11.2 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index e67615d9403a..16c652a16370 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.4.11.1 + 1.4.11.2 diff --git a/package-lock.json b/package-lock.json index 03f67c1bbfdb..40d77bf6f2c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.11-1", + "version": "1.4.11-2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.11-1", + "version": "1.4.11-2", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 2d89574693c8..fee2a0e3cead 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.11-1", + "version": "1.4.11-2", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.",