From 97807c245cb9fd7a45dd2258847019339f04fa8a Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Thu, 10 Aug 2023 10:48:11 +0700 Subject: [PATCH 001/151] Create is leaving client pusher event --- src/ONYXKEYS.js | 1 + src/libs/Pusher/EventType.js | 1 + src/libs/actions/Report.js | 101 +++++++++++++++++++++++++++++++++ src/pages/home/ReportScreen.js | 49 ++++++++++++++++ 4 files changed, 152 insertions(+) diff --git a/src/ONYXKEYS.js b/src/ONYXKEYS.js index 89e6aa9be419..75e22dbbf0d9 100755 --- a/src/ONYXKEYS.js +++ b/src/ONYXKEYS.js @@ -127,6 +127,7 @@ export default { REPORT_DRAFT_COMMENT_NUMBER_OF_LINES: 'reportDraftCommentNumberOfLines_', REPORT_IS_COMPOSER_FULL_SIZE: 'reportIsComposerFullSize_', REPORT_USER_IS_TYPING: 'reportUserIsTyping_', + REPORT_USER_IS_LEAVING_ROOM: 'reportUserIsLeavingRoom_', SECURITY_GROUP: 'securityGroup_', TRANSACTION: 'transactions_', diff --git a/src/libs/Pusher/EventType.js b/src/libs/Pusher/EventType.js index 639e10020fc7..85ccc5e17242 100644 --- a/src/libs/Pusher/EventType.js +++ b/src/libs/Pusher/EventType.js @@ -5,6 +5,7 @@ export default { REPORT_COMMENT: 'reportComment', ONYX_API_UPDATE: 'onyxApiUpdate', + USER_IS_LEAVING_ROOM: 'client-userIsLeavingRoom', USER_IS_TYPING: 'client-userIsTyping', MULTIPLE_EVENTS: 'multipleEvents', MULTIPLE_EVENT_TYPE: { diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 2ac014e22a13..f241c3b5a657 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -79,6 +79,7 @@ Onyx.connect({ const allReports = {}; let conciergeChatReportID; const typingWatchTimers = {}; +const leavingWatchTimers = {}; /** * Get the private pusher channel name for a Report. @@ -111,6 +112,28 @@ function getNormalizedTypingStatus(typingStatus) { return normalizedTypingStatus; } +/** + * There are 2 possibilities that we can receive via pusher for a user's leaving status: + * 1. The "new" way from New Expensify is passed as {[login]: Boolean} (e.g. {yuwen@expensify.com: true}), where the value + * is whether the user with that login is leaving on the report or not. + * 2. The "old" way from e.com which is passed as {userLogin: login} (e.g. {userLogin: bstites@expensify.com}) + * + * This method makes sure that no matter which we get, we return the "new" format + * + * @param {Object} leavingStatus + * @returns {Object} + */ +function getNormalizedLeavingStatus(leavingStatus) { + let normalizedLeavingStatus = leavingStatus; + + if (_.first(_.keys(leavingStatus)) === 'userLogin') { + normalizedLeavingStatus = {[leavingStatus.userLogin]: true}; + } + + return normalizedLeavingStatus; +} + + /** * Initialize our pusher subscriptions to listen for someone typing in a report. * @@ -158,6 +181,53 @@ function subscribeToReportTypingEvents(reportID) { }); } +/** + * Initialize our pusher subscriptions to listen for someone typing in a report. + * + * @param {String} reportID + */ +function subscribeToReportLeavingEvents(reportID) { + if (!reportID) { + return; + } + + // Make sure we have a clean Leaving indicator before subscribing to leaving events + Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_USER_IS_LEAVING_ROOM}${reportID}`, {}); + + const pusherChannelName = getReportChannelName(reportID); + Pusher.subscribe(pusherChannelName, Pusher.TYPE.USER_IS_LEAVING_ROOM, (leavingStatus) => { + // If the pusher message comes from OldDot, we expect the leaving status to be keyed by user + // login OR by 'Concierge'. If the pusher message comes from NewDot, it is keyed by accountID + // since personal details are keyed by accountID. + const normalizedLeavingStatus = getNormalizedLeavingStatus(leavingStatus); + const accountIDOrLogin = _.first(_.keys(normalizedLeavingStatus)); + + if (!accountIDOrLogin) { + return; + } + + // Don't show the leaving indicator if the user is leaving on another platform + if (Number(accountIDOrLogin) === currentUserAccountID) { + return; + } + + // Use a combo of the reportID and the accountID or login as a key for holding our timers. + const reportUserIdentifier = `${reportID}-${accountIDOrLogin}`; + clearTimeout(leavingWatchTimers[reportUserIdentifier]); + Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_USER_IS_LEAVING_ROOM}${reportID}`, normalizedLeavingStatus); + + // Wait for 1.5s of no additional leaving events before setting the status back to false. + leavingWatchTimers[reportUserIdentifier] = setTimeout(() => { + const leavingStoppedStatus = {}; + leavingStoppedStatus[accountIDOrLogin] = false; + Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_USER_IS_LEAVING_ROOM}${reportID}`, leavingStoppedStatus); + delete leavingWatchTimers[reportUserIdentifier]; + }, 1500); + }).catch((error) => { + Log.hmmm('[Report] Failed to initially subscribe to Pusher channel', false, {errorType: error.type, pusherChannelName}); + }); +} + /** * Remove our pusher subscriptions to listen for someone typing in a report. * @@ -173,6 +243,22 @@ function unsubscribeFromReportChannel(reportID) { Pusher.unsubscribe(pusherChannelName, Pusher.TYPE.USER_IS_TYPING); } +/** + * Remove our pusher subscriptions to listen for someone typing in a report. + * + * @param {String} reportID + */ +function unsubscribeFromLeavingRoomReportChannel(reportID) { + if (!reportID) { + return; + } + + const pusherChannelName = getReportChannelName(reportID); + Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_USER_IS_LEAVING_ROOM}${reportID}`, {}); + Pusher.unsubscribe(pusherChannelName, Pusher.TYPE.USER_IS_LEAVING_ROOM); +} + + // New action subscriber array for report pages let newActionSubscribers = []; @@ -838,6 +924,17 @@ function broadcastUserIsTyping(reportID) { typingStatus[currentUserAccountID] = true; Pusher.sendEvent(privateReportChannelName, Pusher.TYPE.USER_IS_TYPING, typingStatus); } +/** + * Broadcasts whether or not a user is typing on a report over the report's private pusher channel. + * + * @param {String} reportID + */ +function broadcastUserIsLeavingRoom(reportID) { + const privateReportChannelName = getReportChannelName(reportID); + const leavingStatus = {}; + leavingStatus[currentUserAccountID] = true; + Pusher.sendEvent(privateReportChannelName, Pusher.TYPE.USER_IS_LEAVING_ROOM, leavingStatus); +} /** * When a report changes in Onyx, this fetches the report from the API if the report doesn't have a name @@ -1763,6 +1860,7 @@ function leaveRoom(reportID) { ], }, ); + broadcastUserIsLeavingRoom() navigateToConciergeChat(); } @@ -1878,10 +1976,13 @@ export { updateWriteCapabilityAndNavigate, updateNotificationPreferenceAndNavigate, subscribeToReportTypingEvents, + subscribeToReportLeavingEvents, unsubscribeFromReportChannel, + unsubscribeFromLeavingRoomReportChannel, saveReportComment, saveReportCommentNumberOfLines, broadcastUserIsTyping, + broadcastUserIsLeavingRoom, togglePinnedState, editReportComment, handleUserDeletedLinksInHtml, diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index c859bc6b8f05..c4d5a61bfbe7 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -83,6 +83,8 @@ const propTypes = { /** All of the personal details for everyone */ personalDetails: PropTypes.objectOf(personalDetailsPropType), + userLeavingStatuses: PropTypes.bool, + ...windowDimensionsPropTypes, ...viewportOffsetTopPropTypes, }; @@ -98,6 +100,7 @@ const defaultProps = { betas: [], policies: {}, accountManagerReportID: null, + userLeavingStatuses: false, personalDetails: {}, }; @@ -125,6 +128,7 @@ class ReportScreen extends React.Component { this.onSubmitComment = this.onSubmitComment.bind(this); this.chatWithAccountManager = this.chatWithAccountManager.bind(this); this.dismissBanner = this.dismissBanner.bind(this); + this.checkAndSubscribe = this.checkAndSubscribe.bind(this); this.state = { skeletonViewContainerHeight: reportActionsListViewHeight, @@ -135,6 +139,8 @@ class ReportScreen extends React.Component { this.flatListRef = React.createRef(); this.reactionListRef = React.createRef(); + + this.didSubscribeToReportLeavingEvents = React.createRef(); } componentDidMount() { @@ -152,14 +158,39 @@ class ReportScreen extends React.Component { this.fetchReportIfNeeded(); ComposerActions.setShouldShowComposeInput(true); + this.checkAndSubscribe(); } componentDidUpdate(prevProps) { + + console.log('[debug] userLeavingStatuses', this.props.userLeavingStatuses) // If composer should be hidden, hide emoji picker as well if (ReportUtils.shouldHideComposer(this.props.report)) { EmojiPickerAction.hideEmojiPicker(true); } + // const onyxReportID = this.props.report.reportID; + // const prevOnyxReportID = prevProps.report.reportID; + // const routeReportID = getReportID(this.props.route); + + // // navigate to concierge when the room removed from another device (e.g. user leaving a room) + // // the report will not really null when removed, it will have defaultProps properties and values + // if ( + // prevOnyxReportID && + // prevOnyxReportID === routeReportID && + // !onyxReportID && + // // non-optimistic case + // (_.isEqual(this.props.report, defaultProps.report) || + // // optimistic case + // (prevProps.report.statusNum === CONST.REPORT.STATUS.OPEN && this.props.report.statusNum === CONST.REPORT.STATUS.CLOSED)) + // ) { + // // Navigation.goBack(); + // // Report.navigateToConciergeChat(); + // // // isReportRemoved will prevent showing when navigating + // // this.setState({isReportRemoved: true}); + // return; + // } + // If you already have a report open and are deeplinking to a new report on native, // the ReportScreen never actually unmounts and the reportID in the route also doesn't change. // Therefore, we need to compare if the existing reportID is the same as the one in the route @@ -172,9 +203,13 @@ class ReportScreen extends React.Component { this.fetchReportIfNeeded(); ComposerActions.setShouldShowComposeInput(true); + this.checkAndSubscribe(); } componentWillUnmount() { + if(this.didSubscribeToReportLeavingEvents){ + Report.unsubscribeFromLeavingRoomReportChannel(this.props.report.reportID); + } if (!this.unsubscribeVisibilityListener) { return; } @@ -228,6 +263,17 @@ class ReportScreen extends React.Component { Navigation.navigate(ROUTES.getReportRoute(this.props.accountManagerReportID)); } + checkAndSubscribe() { + const { report, reportID } = this.props; + + const didCreateReportSuccessfully = !report.pendingFields || (!report.pendingFields.addWorkspaceRoom && !report.pendingFields.createChat); + + if (!this.didSubscribeToReportLeavingEvents.current && didCreateReportSuccessfully) { + Report.subscribeToReportLeavingEvents(reportID); + this.didSubscribeToReportLeavingEvents.current = true; + } + } + render() { const reportID = getReportID(this.props.route); const {addWorkspaceRoomOrChatPendingAction, addWorkspaceRoomOrChatErrors} = ReportUtils.getReportOfflinePendingActionAndErrors(this.props.report); @@ -426,5 +472,8 @@ export default compose( personalDetails: { key: ONYXKEYS.PERSONAL_DETAILS_LIST, }, + userLeavingStatuses: { + key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT_USER_IS_LEAVING_ROOM}${reportID}`, + }, }), )(ReportScreen); From b769dc838a9e04eac975955c1bd69704c70f5e22 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Thu, 10 Aug 2023 14:07:10 +0700 Subject: [PATCH 002/151] implement subscribeToReportLeavingEvents --- src/libs/Pusher/pusher.js | 13 +++++++ src/libs/actions/Report.js | 37 +++++++++++--------- src/pages/home/ReportScreen.js | 63 +++++++++++++++++----------------- 3 files changed, 66 insertions(+), 47 deletions(-) diff --git a/src/libs/Pusher/pusher.js b/src/libs/Pusher/pusher.js index 60587a68e173..5921d8448f51 100644 --- a/src/libs/Pusher/pusher.js +++ b/src/libs/Pusher/pusher.js @@ -195,8 +195,12 @@ function bindEventToChannel(channel, eventName, eventCallback = () => {}) { */ function subscribe(channelName, eventName, eventCallback = () => {}, onResubscribe = () => {}) { return new Promise((resolve, reject) => { + console.log('[debug] jiasjdawd', channelName) + // We cannot call subscribe() before init(). Prevent any attempt to do this on dev. if (!socket) { + console.log('[debug] ohjaihwa', channelName) + throw new Error(`[Pusher] instance not found. Pusher.subscribe() most likely has been called before Pusher.init()`); } @@ -205,11 +209,17 @@ function subscribe(channelName, eventName, eventCallback = () => {}, onResubscri let channel = getChannel(channelName); if (!channel || !channel.subscribed) { + console.log('[debug] noahdwawwad', channelName) + channel = socket.subscribe(channelName); let isBound = false; channel.bind('pusher:subscription_succeeded', () => { + console.log('[debug] ahusyodiasd', channelName) + // Check so that we do not bind another event with each reconnect attempt if (!isBound) { + console.log('[debug] pjaisdh08q', channelName) + bindEventToChannel(channel, eventName, eventCallback); resolve(); isBound = true; @@ -224,6 +234,8 @@ function subscribe(channelName, eventName, eventCallback = () => {}, onResubscri }); channel.bind('pusher:subscription_error', (data = {}) => { + console.log('[debug] jh98c20chc0asc', channelName) + const {type, error, status} = data; Log.hmmm('[Pusher] Issue authenticating with Pusher during subscribe attempt.', { channelName, @@ -234,6 +246,7 @@ function subscribe(channelName, eventName, eventCallback = () => {}, onResubscri reject(error); }); } else { + console.log('[debug] jouahsdiasd', channelName) bindEventToChannel(channel, eventName, eventCallback); resolve(); } diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index f241c3b5a657..4cc5ed0eba50 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -187,42 +187,47 @@ function subscribeToReportTypingEvents(reportID) { * @param {String} reportID */ function subscribeToReportLeavingEvents(reportID) { + console.log('[debug] subscribeToReportLeavingEvents(reportID)', reportID) if (!reportID) { return; } // Make sure we have a clean Leaving indicator before subscribing to leaving events - Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_USER_IS_LEAVING_ROOM}${reportID}`, {}); + Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_USER_IS_LEAVING_ROOM}${reportID}`, false); const pusherChannelName = getReportChannelName(reportID); Pusher.subscribe(pusherChannelName, Pusher.TYPE.USER_IS_LEAVING_ROOM, (leavingStatus) => { + console.log('[debug] leavingStatus', leavingStatus) // If the pusher message comes from OldDot, we expect the leaving status to be keyed by user // login OR by 'Concierge'. If the pusher message comes from NewDot, it is keyed by accountID // since personal details are keyed by accountID. const normalizedLeavingStatus = getNormalizedLeavingStatus(leavingStatus); const accountIDOrLogin = _.first(_.keys(normalizedLeavingStatus)); + console.log('[debug] normalizedLeavingStatus', normalizedLeavingStatus) + console.log('[debug] accountIDOrLogin', accountIDOrLogin) if (!accountIDOrLogin) { return; } - // Don't show the leaving indicator if the user is leaving on another platform - if (Number(accountIDOrLogin) === currentUserAccountID) { + if (Number(accountIDOrLogin) !== currentUserAccountID) { return; } // Use a combo of the reportID and the accountID or login as a key for holding our timers. - const reportUserIdentifier = `${reportID}-${accountIDOrLogin}`; - clearTimeout(leavingWatchTimers[reportUserIdentifier]); - Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_USER_IS_LEAVING_ROOM}${reportID}`, normalizedLeavingStatus); - - // Wait for 1.5s of no additional leaving events before setting the status back to false. - leavingWatchTimers[reportUserIdentifier] = setTimeout(() => { - const leavingStoppedStatus = {}; - leavingStoppedStatus[accountIDOrLogin] = false; - Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_USER_IS_LEAVING_ROOM}${reportID}`, leavingStoppedStatus); - delete leavingWatchTimers[reportUserIdentifier]; - }, 1500); + // const reportUserIdentifier = `${reportID}-${accountIDOrLogin}`; + // clearTimeout(leavingWatchTimers[reportUserIdentifier]); + console.log('[debug] Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_USER_IS_LEAVING_ROOM}${reportID}`, true)'); + Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_USER_IS_LEAVING_ROOM}${reportID}`, true); + // // Wait for 1.5s of no additional leaving events before setting the status back to false. + // leavingWatchTimers[reportUserIdentifier] = setTimeout(() => { + // const leavingStoppedStatus = {}; + // leavingStoppedStatus[accountIDOrLogin] = false; + // console.log('[debug] leavingStoppedStatus', leavingStoppedStatus) + + // Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_USER_IS_LEAVING_ROOM}${reportID}`, leavingStoppedStatus); + // delete leavingWatchTimers[reportUserIdentifier]; + // }, 1500); }).catch((error) => { Log.hmmm('[Report] Failed to initially subscribe to Pusher channel', false, {errorType: error.type, pusherChannelName}); }); @@ -254,7 +259,7 @@ function unsubscribeFromLeavingRoomReportChannel(reportID) { } const pusherChannelName = getReportChannelName(reportID); - Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_USER_IS_LEAVING_ROOM}${reportID}`, {}); + Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_USER_IS_LEAVING_ROOM}${reportID}`, false); Pusher.unsubscribe(pusherChannelName, Pusher.TYPE.USER_IS_LEAVING_ROOM); } @@ -1860,7 +1865,7 @@ function leaveRoom(reportID) { ], }, ); - broadcastUserIsLeavingRoom() + broadcastUserIsLeavingRoom(reportID) navigateToConciergeChat(); } diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index c4d5a61bfbe7..e86df04d54f0 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -37,6 +37,7 @@ import * as ComposerActions from '../../libs/actions/Composer'; import ReportScreenContext from './ReportScreenContext'; import TaskHeaderActionButton from '../../components/TaskHeaderActionButton'; import DragAndDropProvider from '../../components/DragAndDrop/Provider'; +import CONST from '../../CONST'; const propTypes = { /** Navigation route context info provided by react navigation */ @@ -83,7 +84,7 @@ const propTypes = { /** All of the personal details for everyone */ personalDetails: PropTypes.objectOf(personalDetailsPropType), - userLeavingStatuses: PropTypes.bool, + userLeavingStatus: PropTypes.bool, ...windowDimensionsPropTypes, ...viewportOffsetTopPropTypes, @@ -100,7 +101,7 @@ const defaultProps = { betas: [], policies: {}, accountManagerReportID: null, - userLeavingStatuses: false, + userLeavingStatus: false, personalDetails: {}, }; @@ -163,40 +164,39 @@ class ReportScreen extends React.Component { componentDidUpdate(prevProps) { - console.log('[debug] userLeavingStatuses', this.props.userLeavingStatuses) // If composer should be hidden, hide emoji picker as well if (ReportUtils.shouldHideComposer(this.props.report)) { EmojiPickerAction.hideEmojiPicker(true); } - // const onyxReportID = this.props.report.reportID; - // const prevOnyxReportID = prevProps.report.reportID; - // const routeReportID = getReportID(this.props.route); - - // // navigate to concierge when the room removed from another device (e.g. user leaving a room) - // // the report will not really null when removed, it will have defaultProps properties and values - // if ( - // prevOnyxReportID && - // prevOnyxReportID === routeReportID && - // !onyxReportID && - // // non-optimistic case - // (_.isEqual(this.props.report, defaultProps.report) || - // // optimistic case - // (prevProps.report.statusNum === CONST.REPORT.STATUS.OPEN && this.props.report.statusNum === CONST.REPORT.STATUS.CLOSED)) - // ) { - // // Navigation.goBack(); - // // Report.navigateToConciergeChat(); - // // // isReportRemoved will prevent showing when navigating - // // this.setState({isReportRemoved: true}); - // return; - // } + const onyxReportID = this.props.report.reportID; + const prevOnyxReportID = prevProps.report.reportID; + const routeReportID = getReportID(this.props.route); + + console.log('[debug] userLeavingStatus', this.props.userLeavingStatus) + console.log('[debug] prevOnyxReportID', prevOnyxReportID) + console.log('[debug] routeReportID', routeReportID) + console.log('[debug] onyxReportID', onyxReportID) + + // navigate to concierge when the room removed from another device (e.g. user leaving a room) + if ( + // non-optimistic case + (!prevProps.userLeavingStatus && this.props.userLeavingStatus) || + // optimistic case + (prevOnyxReportID && + prevOnyxReportID === routeReportID && + !onyxReportID && + (prevProps.report.statusNum === CONST.REPORT.STATUS.OPEN && this.props.report.statusNum === CONST.REPORT.STATUS.CLOSED)) + ) { + Navigation.goBack(); + Report.navigateToConciergeChat(); + return; + } // If you already have a report open and are deeplinking to a new report on native, // the ReportScreen never actually unmounts and the reportID in the route also doesn't change. // Therefore, we need to compare if the existing reportID is the same as the one in the route // before deciding that we shouldn't call OpenReport. - const onyxReportID = this.props.report.reportID; - const routeReportID = getReportID(this.props.route); if (onyxReportID === prevProps.report.reportID && (!onyxReportID || onyxReportID === routeReportID)) { return; } @@ -264,12 +264,13 @@ class ReportScreen extends React.Component { } checkAndSubscribe() { - const { report, reportID } = this.props; + const {report} = this.props; const didCreateReportSuccessfully = !report.pendingFields || (!report.pendingFields.addWorkspaceRoom && !report.pendingFields.createChat); if (!this.didSubscribeToReportLeavingEvents.current && didCreateReportSuccessfully) { - Report.subscribeToReportLeavingEvents(reportID); + console.log('[debug] Report.subscribeToReportLeavingEvents(reportID);') + Report.subscribeToReportLeavingEvents(report.reportID); this.didSubscribeToReportLeavingEvents.current = true; } } @@ -340,7 +341,7 @@ class ReportScreen extends React.Component { shouldEnableKeyboardAvoidingView={isTopMostReportId} > `${ONYXKEYS.COLLECTION.REPORT_USER_IS_LEAVING_ROOM}${reportID}`, + userLeavingStatus: { + key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT_USER_IS_LEAVING_ROOM}${getReportID(route)}`, }, }), )(ReportScreen); From c87ceb934bd0f4b2f5610ac137f1421ba3df7216 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Thu, 10 Aug 2023 14:29:09 +0700 Subject: [PATCH 003/151] Remove unnecessary code, run prettier --- src/libs/Pusher/pusher.js | 13 ------------- src/libs/actions/Report.js | 21 +-------------------- src/pages/home/ReportScreen.js | 14 +++----------- 3 files changed, 4 insertions(+), 44 deletions(-) diff --git a/src/libs/Pusher/pusher.js b/src/libs/Pusher/pusher.js index 5921d8448f51..60587a68e173 100644 --- a/src/libs/Pusher/pusher.js +++ b/src/libs/Pusher/pusher.js @@ -195,12 +195,8 @@ function bindEventToChannel(channel, eventName, eventCallback = () => {}) { */ function subscribe(channelName, eventName, eventCallback = () => {}, onResubscribe = () => {}) { return new Promise((resolve, reject) => { - console.log('[debug] jiasjdawd', channelName) - // We cannot call subscribe() before init(). Prevent any attempt to do this on dev. if (!socket) { - console.log('[debug] ohjaihwa', channelName) - throw new Error(`[Pusher] instance not found. Pusher.subscribe() most likely has been called before Pusher.init()`); } @@ -209,17 +205,11 @@ function subscribe(channelName, eventName, eventCallback = () => {}, onResubscri let channel = getChannel(channelName); if (!channel || !channel.subscribed) { - console.log('[debug] noahdwawwad', channelName) - channel = socket.subscribe(channelName); let isBound = false; channel.bind('pusher:subscription_succeeded', () => { - console.log('[debug] ahusyodiasd', channelName) - // Check so that we do not bind another event with each reconnect attempt if (!isBound) { - console.log('[debug] pjaisdh08q', channelName) - bindEventToChannel(channel, eventName, eventCallback); resolve(); isBound = true; @@ -234,8 +224,6 @@ function subscribe(channelName, eventName, eventCallback = () => {}, onResubscri }); channel.bind('pusher:subscription_error', (data = {}) => { - console.log('[debug] jh98c20chc0asc', channelName) - const {type, error, status} = data; Log.hmmm('[Pusher] Issue authenticating with Pusher during subscribe attempt.', { channelName, @@ -246,7 +234,6 @@ function subscribe(channelName, eventName, eventCallback = () => {}, onResubscri reject(error); }); } else { - console.log('[debug] jouahsdiasd', channelName) bindEventToChannel(channel, eventName, eventCallback); resolve(); } diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 4cc5ed0eba50..a3a77667bf43 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -133,7 +133,6 @@ function getNormalizedLeavingStatus(leavingStatus) { return normalizedLeavingStatus; } - /** * Initialize our pusher subscriptions to listen for someone typing in a report. * @@ -187,7 +186,6 @@ function subscribeToReportTypingEvents(reportID) { * @param {String} reportID */ function subscribeToReportLeavingEvents(reportID) { - console.log('[debug] subscribeToReportLeavingEvents(reportID)', reportID) if (!reportID) { return; } @@ -197,14 +195,11 @@ function subscribeToReportLeavingEvents(reportID) { const pusherChannelName = getReportChannelName(reportID); Pusher.subscribe(pusherChannelName, Pusher.TYPE.USER_IS_LEAVING_ROOM, (leavingStatus) => { - console.log('[debug] leavingStatus', leavingStatus) // If the pusher message comes from OldDot, we expect the leaving status to be keyed by user // login OR by 'Concierge'. If the pusher message comes from NewDot, it is keyed by accountID // since personal details are keyed by accountID. const normalizedLeavingStatus = getNormalizedLeavingStatus(leavingStatus); const accountIDOrLogin = _.first(_.keys(normalizedLeavingStatus)); - console.log('[debug] normalizedLeavingStatus', normalizedLeavingStatus) - console.log('[debug] accountIDOrLogin', accountIDOrLogin) if (!accountIDOrLogin) { return; @@ -214,20 +209,7 @@ function subscribeToReportLeavingEvents(reportID) { return; } - // Use a combo of the reportID and the accountID or login as a key for holding our timers. - // const reportUserIdentifier = `${reportID}-${accountIDOrLogin}`; - // clearTimeout(leavingWatchTimers[reportUserIdentifier]); - console.log('[debug] Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_USER_IS_LEAVING_ROOM}${reportID}`, true)'); Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_USER_IS_LEAVING_ROOM}${reportID}`, true); - // // Wait for 1.5s of no additional leaving events before setting the status back to false. - // leavingWatchTimers[reportUserIdentifier] = setTimeout(() => { - // const leavingStoppedStatus = {}; - // leavingStoppedStatus[accountIDOrLogin] = false; - // console.log('[debug] leavingStoppedStatus', leavingStoppedStatus) - - // Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_USER_IS_LEAVING_ROOM}${reportID}`, leavingStoppedStatus); - // delete leavingWatchTimers[reportUserIdentifier]; - // }, 1500); }).catch((error) => { Log.hmmm('[Report] Failed to initially subscribe to Pusher channel', false, {errorType: error.type, pusherChannelName}); }); @@ -263,7 +245,6 @@ function unsubscribeFromLeavingRoomReportChannel(reportID) { Pusher.unsubscribe(pusherChannelName, Pusher.TYPE.USER_IS_LEAVING_ROOM); } - // New action subscriber array for report pages let newActionSubscribers = []; @@ -1865,7 +1846,7 @@ function leaveRoom(reportID) { ], }, ); - broadcastUserIsLeavingRoom(reportID) + broadcastUserIsLeavingRoom(reportID); navigateToConciergeChat(); } diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index e86df04d54f0..7d817af71524 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -163,7 +163,6 @@ class ReportScreen extends React.Component { } componentDidUpdate(prevProps) { - // If composer should be hidden, hide emoji picker as well if (ReportUtils.shouldHideComposer(this.props.report)) { EmojiPickerAction.hideEmojiPicker(true); @@ -173,11 +172,6 @@ class ReportScreen extends React.Component { const prevOnyxReportID = prevProps.report.reportID; const routeReportID = getReportID(this.props.route); - console.log('[debug] userLeavingStatus', this.props.userLeavingStatus) - console.log('[debug] prevOnyxReportID', prevOnyxReportID) - console.log('[debug] routeReportID', routeReportID) - console.log('[debug] onyxReportID', onyxReportID) - // navigate to concierge when the room removed from another device (e.g. user leaving a room) if ( // non-optimistic case @@ -186,7 +180,8 @@ class ReportScreen extends React.Component { (prevOnyxReportID && prevOnyxReportID === routeReportID && !onyxReportID && - (prevProps.report.statusNum === CONST.REPORT.STATUS.OPEN && this.props.report.statusNum === CONST.REPORT.STATUS.CLOSED)) + prevProps.report.statusNum === CONST.REPORT.STATUS.OPEN && + this.props.report.statusNum === CONST.REPORT.STATUS.CLOSED) ) { Navigation.goBack(); Report.navigateToConciergeChat(); @@ -207,7 +202,7 @@ class ReportScreen extends React.Component { } componentWillUnmount() { - if(this.didSubscribeToReportLeavingEvents){ + if (this.didSubscribeToReportLeavingEvents) { Report.unsubscribeFromLeavingRoomReportChannel(this.props.report.reportID); } if (!this.unsubscribeVisibilityListener) { @@ -265,11 +260,8 @@ class ReportScreen extends React.Component { checkAndSubscribe() { const {report} = this.props; - const didCreateReportSuccessfully = !report.pendingFields || (!report.pendingFields.addWorkspaceRoom && !report.pendingFields.createChat); - if (!this.didSubscribeToReportLeavingEvents.current && didCreateReportSuccessfully) { - console.log('[debug] Report.subscribeToReportLeavingEvents(reportID);') Report.subscribeToReportLeavingEvents(report.reportID); this.didSubscribeToReportLeavingEvents.current = true; } From b57b046624adb8dac585064e016af2956d41cfe3 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Thu, 10 Aug 2023 14:42:01 +0700 Subject: [PATCH 004/151] refine comment, remove leavingWatchTimers variable, refine checkAndSubscribe naming --- src/libs/actions/Report.js | 5 ++--- src/pages/home/ReportScreen.js | 9 +++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index a3a77667bf43..de1fe0eb4213 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -79,7 +79,6 @@ Onyx.connect({ const allReports = {}; let conciergeChatReportID; const typingWatchTimers = {}; -const leavingWatchTimers = {}; /** * Get the private pusher channel name for a Report. @@ -231,7 +230,7 @@ function unsubscribeFromReportChannel(reportID) { } /** - * Remove our pusher subscriptions to listen for someone typing in a report. + * Remove our pusher subscriptions to listen for someone leaving a report. * * @param {String} reportID */ @@ -911,7 +910,7 @@ function broadcastUserIsTyping(reportID) { Pusher.sendEvent(privateReportChannelName, Pusher.TYPE.USER_IS_TYPING, typingStatus); } /** - * Broadcasts whether or not a user is typing on a report over the report's private pusher channel. + * Broadcasts whether or not a user is leaving on a report over the report's private pusher channel. * * @param {String} reportID */ diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 7d817af71524..dca4a74daf8a 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -84,6 +84,7 @@ const propTypes = { /** All of the personal details for everyone */ personalDetails: PropTypes.objectOf(personalDetailsPropType), + /** Whether user leaving current report that listen to another device leaveRoom trigger */ userLeavingStatus: PropTypes.bool, ...windowDimensionsPropTypes, @@ -129,7 +130,7 @@ class ReportScreen extends React.Component { this.onSubmitComment = this.onSubmitComment.bind(this); this.chatWithAccountManager = this.chatWithAccountManager.bind(this); this.dismissBanner = this.dismissBanner.bind(this); - this.checkAndSubscribe = this.checkAndSubscribe.bind(this); + this.checkAndSubscribeReportLeavingEvents = this.checkAndSubscribeReportLeavingEvents.bind(this); this.state = { skeletonViewContainerHeight: reportActionsListViewHeight, @@ -159,7 +160,7 @@ class ReportScreen extends React.Component { this.fetchReportIfNeeded(); ComposerActions.setShouldShowComposeInput(true); - this.checkAndSubscribe(); + this.checkAndSubscribeReportLeavingEvents(); } componentDidUpdate(prevProps) { @@ -198,7 +199,7 @@ class ReportScreen extends React.Component { this.fetchReportIfNeeded(); ComposerActions.setShouldShowComposeInput(true); - this.checkAndSubscribe(); + this.checkAndSubscribeReportLeavingEvents(); } componentWillUnmount() { @@ -258,7 +259,7 @@ class ReportScreen extends React.Component { Navigation.navigate(ROUTES.getReportRoute(this.props.accountManagerReportID)); } - checkAndSubscribe() { + checkAndSubscribeReportLeavingEvents() { const {report} = this.props; const didCreateReportSuccessfully = !report.pendingFields || (!report.pendingFields.addWorkspaceRoom && !report.pendingFields.createChat); if (!this.didSubscribeToReportLeavingEvents.current && didCreateReportSuccessfully) { From 6baddfe4f3ec43f9e97fe3ff934828c310dcf1b4 Mon Sep 17 00:00:00 2001 From: Edu Date: Wed, 16 Aug 2023 10:19:45 -0300 Subject: [PATCH 005/151] adding script to find unused keys --- package.json | 3 +- scripts/find-unused-keys.sh | 91 +++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 1 deletion(-) create mode 100755 scripts/find-unused-keys.sh diff --git a/package.json b/package.json index 00d8c2f027fe..9454f8e982aa 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,8 @@ "analyze-packages": "ANALYZE_BUNDLE=true webpack --config config/webpack/webpack.common.js --env envFile=.env.production", "symbolicate:android": "npx metro-symbolicate android/app/build/generated/sourcemaps/react/release/index.android.bundle.map", "symbolicate:ios": "npx metro-symbolicate main.jsbundle.map", - "test:e2e": "node tests/e2e/testRunner.js --development" + "test:e2e": "node tests/e2e/testRunner.js --development", + "find-missing-keys": "scripts/find-unused-keys.sh" }, "dependencies": { "@expensify/react-native-web": "0.18.15", diff --git a/scripts/find-unused-keys.sh b/scripts/find-unused-keys.sh new file mode 100755 index 000000000000..acfd9be1a191 --- /dev/null +++ b/scripts/find-unused-keys.sh @@ -0,0 +1,91 @@ +#!/bin/bash + +# Configurations +SRC_DIR="src" +STYLES_FILE="src/styles/styles.js" +TRANSLATION_FILES=("src/languages/es.js" "src/languages/en.js") +KEYS_LIST_FILE="keys_list.txt" + +# Function to find and store keys from a file +find_and_store_keys() { + local file="$1" + local file_keys=($(grep -Eo "([a-zA-Z0-9_-]+\.)?[a-zA-Z0-9_-]+:" "$file" | sed -E "s/[:]//g")) + + for key in "${file_keys[@]}"; do + local line_numbers=($(grep -n "$key" "$file" | cut -d':' -f1)) + for line_number in "${line_numbers[@]}"; do + echo "$key:$file:$line_number" + done + done +} + +# Function to remove keys from the list +remove_keys() { + local file="$1" + local list_file="$2" + + while IFS= read -r key_info; do + local key=$(echo "$key_info" | cut -d':' -f1) + local key_file=$(echo "$key_info" | cut -d':' -f2) + if [[ "$key_file" != "$file" ]]; then + echo "$key_info" + fi + done < "$list_file" +} + +# Function to find unused keys in a file +find_unused_keys_in_file() { + local file="$1" + local list_file="$2" + local unused_keys=() + + while IFS= read -r key_info; do + local key=$(echo "$key_info" | cut -d':' -f1) + local key_file=$(echo "$key_info" | cut -d':' -f2) + local line_number=$(echo "$key_info" | cut -d':' -f3) + if [[ "$key_file" != "$file" ]]; then + continue + fi + + if ! grep -q "$key" "$file"; then + # Check if the line number contains a numeric value + if [[ "$line_number" =~ ^[0-9]+$ ]]; then + unused_keys+=("$key_info") + fi + fi + done < "$list_file" + + for unused_key_info in "${unused_keys[@]}"; do + echo "Error: Unused key '$(echo "$unused_key_info" | cut -d':' -f1)' found in '$file' at line: $(echo "$unused_key_info" | cut -d':' -f3)" + done +} + +# Find and store keys from styles.js (only top-level keys) +grep -Eo "^[[:space:]]*[a-zA-Z0-9_-]+:" "$STYLES_FILE" | sed -E "s/[:]//g" | while IFS= read -r key; do + echo "$key:$STYLES_FILE:0" +done > "$KEYS_LIST_FILE" + +# Find and store keys from translation files +for translation_file in "${TRANSLATION_FILES[@]}"; do + find_and_store_keys "$translation_file" >> "$KEYS_LIST_FILE" +done + +# Find and remove used keys from the list +while IFS= read -r file; do + remove_keys "$file" "$KEYS_LIST_FILE" > keys_list_temp.txt + mv keys_list_temp.txt "$KEYS_LIST_FILE" +done < <(find "$SRC_DIR" -type f) + +# Find unused keys in all files +unused_keys_found=false +while IFS= read -r file; do + unused_keys_in_file=$(find_unused_keys_in_file "$file" "$KEYS_LIST_FILE") + if [[ -n "$unused_keys_in_file" ]]; then + unused_keys_found=true + echo "$unused_keys_in_file" + fi +done < <(find "$SRC_DIR" -type f) + +if [[ "$unused_keys_found" = false ]]; then + echo "No unused keys found." +fi \ No newline at end of file From d798ea6a0a53eb548469a1b6d4a568136de2a6a6 Mon Sep 17 00:00:00 2001 From: Edu Date: Fri, 18 Aug 2023 17:31:12 -0300 Subject: [PATCH 006/151] Separated into 2 functions to find and parse the object names styles and en/es files --- scripts/find-unused-keys.sh | 143 ++++++++++++++++++++---------------- 1 file changed, 78 insertions(+), 65 deletions(-) diff --git a/scripts/find-unused-keys.sh b/scripts/find-unused-keys.sh index acfd9be1a191..de13dccba812 100755 --- a/scripts/find-unused-keys.sh +++ b/scripts/find-unused-keys.sh @@ -4,88 +4,101 @@ SRC_DIR="src" STYLES_FILE="src/styles/styles.js" TRANSLATION_FILES=("src/languages/es.js" "src/languages/en.js") -KEYS_LIST_FILE="keys_list.txt" +STYLES_KEYS_FILE="src/languages/style_keys_list_temp.txt" +TRANSLATION_KEYS_FILE="src/languages/translations_keys_list_temp.txt" # Function to find and store keys from a file -find_and_store_keys() { +find_styles_and_store_keys() { local file="$1" - local file_keys=($(grep -Eo "([a-zA-Z0-9_-]+\.)?[a-zA-Z0-9_-]+:" "$file" | sed -E "s/[:]//g")) - - for key in "${file_keys[@]}"; do - local line_numbers=($(grep -n "$key" "$file" | cut -d':' -f1)) - for line_number in "${line_numbers[@]}"; do - echo "$key:$file:$line_number" - done - done -} + local parent_keys=() + local root_key="" + local line_number=0 # Initialize the line number -# Function to remove keys from the list -remove_keys() { - local file="$1" - local list_file="$2" - - while IFS= read -r key_info; do - local key=$(echo "$key_info" | cut -d':' -f1) - local key_file=$(echo "$key_info" | cut -d':' -f2) - if [[ "$key_file" != "$file" ]]; then - echo "$key_info" + while IFS= read -r line; do + ((line_number++)) # Increment the line number + + # Skip lines that are not key-related + if [[ ! "$line" =~ ^[[:space:]]*const[[:space:]]+([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*\{|^[[:space:]]*([a-zA-Z0-9_-]+\.)?[a-zA-Z0-9_-]+:[[:space:]]*\{|^[[:space:]]*\} ]]; then + continue fi - done < "$list_file" + + if [[ "$line" =~ ^[[:space:]]*const[[:space:]]+([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*\{ ]]; then + root_key=$(echo "${BASH_REMATCH[1]}" | sed -E "s/[:[:space:]]*\{.*//") + elif [[ "$line" =~ ^[[:space:]]*([a-zA-Z0-9_-]+\.)?[a-zA-Z0-9_-]+:[[:space:]]*\{ ]]; then + local key=$(echo "$line" | sed -E "s/[:[:space:]]*\{.*//") + # local line_number=$(echo "$line" | grep -n "$key:" | cut -d':' -f1) + + if [[ ${#parent_keys[@]} -gt 0 ]]; then + parent_key_trimmed="${parent_keys[${#parent_keys[@]}-1]// /}" # Trim spaces + key_trimmed="${key// /}" # Trim spaces + key="$parent_key_trimmed.$key_trimmed" + elif [[ -n "$root_key" ]]; then + parent_key_trimmed="${root_key// /}" # Trim spaces + key_trimmed="${key// /}" # Trim spaces + key="$parent_key_trimmed.$key_trimmed" + fi + + echo "$key:$file:$line_number" >> "$STYLES_KEYS_FILE" + parent_keys+=("$key") + elif [[ "$line" =~ ^[[:space:]]*\} ]]; then + # unset "parent_keys[${#parent_keys[@]}-1]" + parent_keys=("${parent_keys[@]:0:${#parent_keys[@]}-1}") + fi + # done < <(grep -E "^[[:space:]]*const[[:space:]]+([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*\{|^[[:space:]]*([a-zA-Z0-9_-]+\.)?[a-zA-Z0-9_-]+:[[:space:]]*\{|^[[:space:]]*\}" "$file") + done < "$file" } -# Function to find unused keys in a file -find_unused_keys_in_file() { +find_translations_and_store_keys() { local file="$1" - local list_file="$2" - local unused_keys=() - - while IFS= read -r key_info; do - local key=$(echo "$key_info" | cut -d':' -f1) - local key_file=$(echo "$key_info" | cut -d':' -f2) - local line_number=$(echo "$key_info" | cut -d':' -f3) - if [[ "$key_file" != "$file" ]]; then + local parent_key=() + local current_key="" + local line_number=0 # Initialize the line number + + while IFS= read -r line; do + ((line_number++)) # Increment the line number + + # Skip lines that are not key-related + if [[ ! "$line" =~ ^[[:space:]]*([a-zA-Z0-9_-]+)[[:space:]]*:[[:space:]]*\{|^[[:space:]]*([a-zA-Z0-9_-]+)[[:space:]]*:[[:space:]]*(\'[^\']*\'|\{)|^[[:space:]]*\} ]]; then continue fi + - if ! grep -q "$key" "$file"; then - # Check if the line number contains a numeric value - if [[ "$line_number" =~ ^[0-9]+$ ]]; then - unused_keys+=("$key_info") + if [[ "$line" =~ ^[[:space:]]*([a-zA-Z0-9_-]+)[[:space:]]*:[[:space:]]*\{ ]]; then + local key="${BASH_REMATCH[1]}" + current_key="$key" + + if [[ ${#parent_keys[@]} -gt 0 ]]; then + local parent_key="${parent_keys[*]}" + current_key="$parent_key.$key" + fi + + parent_keys=("${parent_keys[@]}" "$current_key") + elif [[ "$line" =~ ^[[:space:]]*([a-zA-Z0-9_-]+)[[:space:]]*:[[:space:]]*(\'[^\']*\'|\{) ]]; then + local key="${BASH_REMATCH[1]}" + # local line_number=$(echo "$line" | grep -n "${BASH_REMATCH[1]}" | cut -d':' -f1) + + if [[ ${#parent_keys[@]} -gt 0 ]]; then + local lastItem="${#parent_keys[@]}-1" + local parent_key="${parent_keys[$lastItem]}" + + echo "${parent_key}.${key}:${file}:${line_number}" >> "$TRANSLATION_KEYS_FILE" + else + echo "$key:${file}:${line_number}" >> "$TRANSLATION_KEYS_FILE" fi + elif [[ "$line" =~ ^[[:space:]]*\} ]]; then + parent_keys=("${parent_keys[@]:0:${#parent_keys[@]}-1}") + current_key="${parent_keys[*]}" fi - done < "$list_file" - - for unused_key_info in "${unused_keys[@]}"; do - echo "Error: Unused key '$(echo "$unused_key_info" | cut -d':' -f1)' found in '$file' at line: $(echo "$unused_key_info" | cut -d':' -f3)" - done + # done < <(grep -E "^[[:space:]]*([a-zA-Z0-9_-]+)[[:space:]]*:[[:space:]]*\{|^[[:space:]]*([a-zA-Z0-9_-]+)[[:space:]]*:[[:space:]]*(\'[^\']*\'|\{)|^[[:space:]]*\}" "$file") + done < "$file" } -# Find and store keys from styles.js (only top-level keys) -grep -Eo "^[[:space:]]*[a-zA-Z0-9_-]+:" "$STYLES_FILE" | sed -E "s/[:]//g" | while IFS= read -r key; do - echo "$key:$STYLES_FILE:0" -done > "$KEYS_LIST_FILE" +# Find and store keys from styles.js +find_styles_and_store_keys "$STYLES_FILE" # Find and store keys from translation files for translation_file in "${TRANSLATION_FILES[@]}"; do - find_and_store_keys "$translation_file" >> "$KEYS_LIST_FILE" + find_translations_and_store_keys "$translation_file" done -# Find and remove used keys from the list -while IFS= read -r file; do - remove_keys "$file" "$KEYS_LIST_FILE" > keys_list_temp.txt - mv keys_list_temp.txt "$KEYS_LIST_FILE" -done < <(find "$SRC_DIR" -type f) - -# Find unused keys in all files -unused_keys_found=false -while IFS= read -r file; do - unused_keys_in_file=$(find_unused_keys_in_file "$file" "$KEYS_LIST_FILE") - if [[ -n "$unused_keys_in_file" ]]; then - unused_keys_found=true - echo "$unused_keys_in_file" - fi -done < <(find "$SRC_DIR" -type f) - -if [[ "$unused_keys_found" = false ]]; then - echo "No unused keys found." -fi \ No newline at end of file +echo "Keys saved to $KEYS_FILE" \ No newline at end of file From ea6c6fe36cc6138485c1ebb6b1297aba7b7b23db Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Wed, 23 Aug 2023 09:13:36 +0700 Subject: [PATCH 007/151] Move REPORT_USER_IS_LEAVING_ROOM to ONYXKEYS.ts --- src/ONYXKEYS.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 3c0b3ee9a6d6..686d2546bf59 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -233,6 +233,7 @@ const ONYXKEYS = { REPORT_DRAFT_COMMENT_NUMBER_OF_LINES: 'reportDraftCommentNumberOfLines_', REPORT_IS_COMPOSER_FULL_SIZE: 'reportIsComposerFullSize_', REPORT_USER_IS_TYPING: 'reportUserIsTyping_', + REPORT_USER_IS_LEAVING_ROOM: 'reportUserIsLeavingRoom_', SECURITY_GROUP: 'securityGroup_', TRANSACTION: 'transactions_', @@ -361,6 +362,7 @@ type OnyxValues = { [ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT_NUMBER_OF_LINES]: number; [ONYXKEYS.COLLECTION.REPORT_IS_COMPOSER_FULL_SIZE]: boolean; [ONYXKEYS.COLLECTION.REPORT_USER_IS_TYPING]: boolean; + [ONYXKEYS.COLLECTION.REPORT_USER_IS_LEAVING_ROOM]: boolean; [ONYXKEYS.COLLECTION.SECURITY_GROUP]: OnyxTypes.SecurityGroup; [ONYXKEYS.COLLECTION.TRANSACTION]: OnyxTypes.Transaction; From e4d1363e973158876dc1c7c5d9c758ad8a5e6882 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Fri, 25 Aug 2023 14:35:39 +0700 Subject: [PATCH 008/151] Remove unnecessary code --- src/pages/home/ReportScreen.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 598d13ab9f58..d42886aeeb2b 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -163,10 +163,6 @@ class ReportScreen extends React.Component { } componentDidUpdate(prevProps) { - if (ReportUtils.shouldDisableWriteActions(this.props.report)) { - EmojiPickerAction.hideEmojiPicker(true); - } - const onyxReportID = this.props.report.reportID; const prevOnyxReportID = prevProps.report.reportID; const routeReportID = getReportID(this.props.route); From 4ff465faced849a23da24f9929793602dc9226b2 Mon Sep 17 00:00:00 2001 From: Edu Date: Mon, 28 Aug 2023 17:12:07 +0200 Subject: [PATCH 009/151] Going through the codebase and get keys from the files --- scripts/find-unused-keys.sh | 50 ++++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/scripts/find-unused-keys.sh b/scripts/find-unused-keys.sh index de13dccba812..f8515ff3e17f 100755 --- a/scripts/find-unused-keys.sh +++ b/scripts/find-unused-keys.sh @@ -4,8 +4,34 @@ SRC_DIR="src" STYLES_FILE="src/styles/styles.js" TRANSLATION_FILES=("src/languages/es.js" "src/languages/en.js") -STYLES_KEYS_FILE="src/languages/style_keys_list_temp.txt" -TRANSLATION_KEYS_FILE="src/languages/translations_keys_list_temp.txt" +STYLES_KEYS_FILE="scripts/style_keys_list_temp.txt" +TRANSLATION_KEYS_FILE="scripts/translations_keys_list_temp.txt" +REMOVAL_KEYS_FILE="scripts/removal_keys_list_temp.txt" + # Create an empty temp file if it doesn't exist + if [ ! -f "$REMOVAL_KEYS_FILE" ]; then + touch "$REMOVAL_KEYS_FILE" + fi +# Function to remove a keyword from the temp file +remove_keyword() { + + keyword="$1" + # echo "Removing $keyword" + grep -v "$keyword" "$STYLES_KEYS_FILE" > "$REMOVAL_KEYS_FILE" + mv "$REMOVAL_KEYS_FILE" "$STYLES_KEYS_FILE" +} + +lookfor_unused_keywords() { + # Loop through all files in the src folder + find src -type f -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" | while read -r file; do + # echo "Checking $file" + # Search for keywords starting with "styles" + grep -o '\bstyles\.[a-zA-Z0-9_.]*' "$file" | while IFS= read -r keyword; do + # Remove any [ ] characters from the keyword + clean_keyword="${keyword//[\[\]]/}" + remove_keyword "$clean_keyword" + done + done +} # Function to find and store keys from a file find_styles_and_store_keys() { @@ -97,8 +123,20 @@ find_translations_and_store_keys() { find_styles_and_store_keys "$STYLES_FILE" # Find and store keys from translation files -for translation_file in "${TRANSLATION_FILES[@]}"; do - find_translations_and_store_keys "$translation_file" -done +# for translation_file in "${TRANSLATION_FILES[@]}"; do +# find_translations_and_store_keys "$translation_file" +# done + +echo "Keys saved to $KEYS_FILE" +echo "Now go through the list and remove the keys that are used." + +line_count=$(wc -l < $STYLES_KEYS_FILE) +echo "Number of lines in the file: $line_count" + +lookfor_unused_keywords + +echo "Unused keys are into to $STYLES_KEYS_FILE" -echo "Keys saved to $KEYS_FILE" \ No newline at end of file +line_count2=$(wc -l < $STYLES_KEYS_FILE) +echo "Number of lines in the file: $line_count2" +# cat "$STYLES_KEYS_FILE" \ No newline at end of file From fd39606656ba36778e293292ecbfd49a5feaa893 Mon Sep 17 00:00:00 2001 From: Edu Date: Tue, 29 Aug 2023 16:30:12 +0200 Subject: [PATCH 010/151] showing unused style keys --- scripts/find-unused-keys.sh | 51 +++++++++++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/scripts/find-unused-keys.sh b/scripts/find-unused-keys.sh index f8515ff3e17f..38ec0c8b6ed6 100755 --- a/scripts/find-unused-keys.sh +++ b/scripts/find-unused-keys.sh @@ -11,24 +11,58 @@ REMOVAL_KEYS_FILE="scripts/removal_keys_list_temp.txt" if [ ! -f "$REMOVAL_KEYS_FILE" ]; then touch "$REMOVAL_KEYS_FILE" fi + +# Read the style file with unused keys +show_unused_style_keywords() { + while IFS=: read -r key file line_number; do + line_count=$(wc -l < $STYLES_KEYS_FILE) + echo "Unused keys: $line_count" + echo "File: $file" + + # Get lines before and after the error line + lines_before=$((line_number - 3)) + lines_after=$((line_number + 3)) + + # Print context of the error line + echo "Context around line $line_number:" + sed -n "$lines_before,$lines_after p" "$file" | awk -v key="$key" '{gsub(key, "\033[1;31m"key"\033[0m"); print}' + + echo "Unused key: $key" + echo "--------------------------------" + done < "$STYLES_KEYS_FILE" +} + # Function to remove a keyword from the temp file remove_keyword() { keyword="$1" # echo "Removing $keyword" grep -v "$keyword" "$STYLES_KEYS_FILE" > "$REMOVAL_KEYS_FILE" + line_count=$(wc -l < $REMOVAL_KEYS_FILE) + # echo "$REMOVAL_KEYS_FILE lines in the file: $line_count" mv "$REMOVAL_KEYS_FILE" "$STYLES_KEYS_FILE" + # echo "$STYLES_KEYS_FILE UPDATED lines in the file: $line_count" } lookfor_unused_keywords() { # Loop through all files in the src folder - find src -type f -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" | while read -r file; do + find 'src' -type f -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" | while read -r file; do # echo "Checking $file" # Search for keywords starting with "styles" - grep -o '\bstyles\.[a-zA-Z0-9_.]*' "$file" | while IFS= read -r keyword; do + # grep -o '\bstyles\.[a-zA-Z0-9_.]*' "$file" | while IFS= read -r keyword; do + grep -E -o '\bstyles\.[a-zA-Z0-9_.]*' "$file" | grep -v '\/\/' | grep -vE '\/\*.*\*\/' | while IFS= read -r keyword; do + # Remove any [ ] characters from the keyword + # echo "File: $file" clean_keyword="${keyword//[\[\]]/}" - remove_keyword "$clean_keyword" + # skip styles. keyword that might be used in comments + if [[ "$clean_keyword" == "styles." ]]; then + continue + fi + # echo "Found $clean_keyword" + # Remove the keyword from the temp file + remove_keyword "$clean_keyword" + done done } @@ -64,7 +98,12 @@ find_styles_and_store_keys() { key="$parent_key_trimmed.$key_trimmed" fi - echo "$key:$file:$line_number" >> "$STYLES_KEYS_FILE" + # echo "$key:$file:$line_number" >> "$STYLES_KEYS_FILE" + if [[ "$key" == "styles."* ]]; then + echo "$key:$file:$line_number" >> "$STYLES_KEYS_FILE" + else + echo "styles.$key:$file:$line_number" >> "$STYLES_KEYS_FILE" + fi parent_keys+=("$key") elif [[ "$line" =~ ^[[:space:]]*\} ]]; then # unset "parent_keys[${#parent_keys[@]}-1]" @@ -139,4 +178,6 @@ echo "Unused keys are into to $STYLES_KEYS_FILE" line_count2=$(wc -l < $STYLES_KEYS_FILE) echo "Number of lines in the file: $line_count2" -# cat "$STYLES_KEYS_FILE" \ No newline at end of file +# cat "$STYLES_KEYS_FILE" + +show_unused_style_keywords \ No newline at end of file From 96d1c4509e023f117974390bf10277cff7a0cfca Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Wed, 30 Aug 2023 06:24:53 +0700 Subject: [PATCH 011/151] Add prevReport.statusNum to dependency --- src/pages/home/ReportScreen.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index ac96ed39bf4f..a1c280a498dd 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -308,7 +308,7 @@ function ReportScreen({ fetchReportIfNeeded(); ComposerActions.setShouldShowComposeInput(true); - }, [route, report, errors, fetchReportIfNeeded, prevReport.reportID, userLeavingStatus]); + }, [route, report, errors, fetchReportIfNeeded, prevReport.reportID, userLeavingStatus, prevReport.statusNum]); useEffect(() => { // Ensures subscription event succeeds when the report/workspace room is created optimistically. From d779eecd92326ab69a08bcaee194536412ba7b90 Mon Sep 17 00:00:00 2001 From: Eduardo Date: Fri, 1 Sep 2023 08:05:43 +0200 Subject: [PATCH 012/151] removed translation code --- scripts/find-unused-keys.sh | 84 +++++++------------------------------ 1 file changed, 16 insertions(+), 68 deletions(-) diff --git a/scripts/find-unused-keys.sh b/scripts/find-unused-keys.sh index 38ec0c8b6ed6..b99e28d3fe8a 100755 --- a/scripts/find-unused-keys.sh +++ b/scripts/find-unused-keys.sh @@ -3,14 +3,14 @@ # Configurations SRC_DIR="src" STYLES_FILE="src/styles/styles.js" -TRANSLATION_FILES=("src/languages/es.js" "src/languages/en.js") STYLES_KEYS_FILE="scripts/style_keys_list_temp.txt" TRANSLATION_KEYS_FILE="scripts/translations_keys_list_temp.txt" REMOVAL_KEYS_FILE="scripts/removal_keys_list_temp.txt" - # Create an empty temp file if it doesn't exist - if [ ! -f "$REMOVAL_KEYS_FILE" ]; then - touch "$REMOVAL_KEYS_FILE" - fi + +# Create an empty temp file if it doesn't exist +if [ ! -f "$REMOVAL_KEYS_FILE" ]; then + touch "$REMOVAL_KEYS_FILE" +fi # Read the style file with unused keys show_unused_style_keywords() { @@ -34,35 +34,28 @@ show_unused_style_keywords() { # Function to remove a keyword from the temp file remove_keyword() { - keyword="$1" - # echo "Removing $keyword" + grep -v "$keyword" "$STYLES_KEYS_FILE" > "$REMOVAL_KEYS_FILE" line_count=$(wc -l < $REMOVAL_KEYS_FILE) - # echo "$REMOVAL_KEYS_FILE lines in the file: $line_count" mv "$REMOVAL_KEYS_FILE" "$STYLES_KEYS_FILE" - # echo "$STYLES_KEYS_FILE UPDATED lines in the file: $line_count" } lookfor_unused_keywords() { # Loop through all files in the src folder find 'src' -type f -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" | while read -r file; do - # echo "Checking $file" + # Search for keywords starting with "styles" - # grep -o '\bstyles\.[a-zA-Z0-9_.]*' "$file" | while IFS= read -r keyword; do grep -E -o '\bstyles\.[a-zA-Z0-9_.]*' "$file" | grep -v '\/\/' | grep -vE '\/\*.*\*\/' | while IFS= read -r keyword; do # Remove any [ ] characters from the keyword - # echo "File: $file" clean_keyword="${keyword//[\[\]]/}" # skip styles. keyword that might be used in comments if [[ "$clean_keyword" == "styles." ]]; then continue fi - # echo "Found $clean_keyword" - # Remove the keyword from the temp file - remove_keyword "$clean_keyword" + remove_keyword "$clean_keyword" done done } @@ -87,6 +80,12 @@ find_styles_and_store_keys() { elif [[ "$line" =~ ^[[:space:]]*([a-zA-Z0-9_-]+\.)?[a-zA-Z0-9_-]+:[[:space:]]*\{ ]]; then local key=$(echo "$line" | sed -E "s/[:[:space:]]*\{.*//") # local line_number=$(echo "$line" | grep -n "$key:" | cut -d':' -f1) + echo "line $line" + # Handle keys defined in functions within objects + function_key_pattern="[a-zA-Z0-9_-]+:[[:space:]]*\\(.*\\)[[:space:]]*=>[[:space:]]*\\{" + if [[ "$line" =~ $function_key_pattern ]]; then + key="${BASH_REMATCH[0]%%:*}" + fi if [[ ${#parent_keys[@]} -gt 0 ]]; then parent_key_trimmed="${parent_keys[${#parent_keys[@]}-1]// /}" # Trim spaces @@ -113,71 +112,20 @@ find_styles_and_store_keys() { done < "$file" } -find_translations_and_store_keys() { - local file="$1" - local parent_key=() - local current_key="" - local line_number=0 # Initialize the line number - - while IFS= read -r line; do - ((line_number++)) # Increment the line number - - # Skip lines that are not key-related - if [[ ! "$line" =~ ^[[:space:]]*([a-zA-Z0-9_-]+)[[:space:]]*:[[:space:]]*\{|^[[:space:]]*([a-zA-Z0-9_-]+)[[:space:]]*:[[:space:]]*(\'[^\']*\'|\{)|^[[:space:]]*\} ]]; then - continue - fi - - - if [[ "$line" =~ ^[[:space:]]*([a-zA-Z0-9_-]+)[[:space:]]*:[[:space:]]*\{ ]]; then - local key="${BASH_REMATCH[1]}" - current_key="$key" - - if [[ ${#parent_keys[@]} -gt 0 ]]; then - local parent_key="${parent_keys[*]}" - current_key="$parent_key.$key" - fi - - parent_keys=("${parent_keys[@]}" "$current_key") - elif [[ "$line" =~ ^[[:space:]]*([a-zA-Z0-9_-]+)[[:space:]]*:[[:space:]]*(\'[^\']*\'|\{) ]]; then - local key="${BASH_REMATCH[1]}" - # local line_number=$(echo "$line" | grep -n "${BASH_REMATCH[1]}" | cut -d':' -f1) - - if [[ ${#parent_keys[@]} -gt 0 ]]; then - local lastItem="${#parent_keys[@]}-1" - local parent_key="${parent_keys[$lastItem]}" - - echo "${parent_key}.${key}:${file}:${line_number}" >> "$TRANSLATION_KEYS_FILE" - else - echo "$key:${file}:${line_number}" >> "$TRANSLATION_KEYS_FILE" - fi - elif [[ "$line" =~ ^[[:space:]]*\} ]]; then - parent_keys=("${parent_keys[@]:0:${#parent_keys[@]}-1}") - current_key="${parent_keys[*]}" - fi - # done < <(grep -E "^[[:space:]]*([a-zA-Z0-9_-]+)[[:space:]]*:[[:space:]]*\{|^[[:space:]]*([a-zA-Z0-9_-]+)[[:space:]]*:[[:space:]]*(\'[^\']*\'|\{)|^[[:space:]]*\}" "$file") - done < "$file" -} - # Find and store keys from styles.js find_styles_and_store_keys "$STYLES_FILE" -# Find and store keys from translation files -# for translation_file in "${TRANSLATION_FILES[@]}"; do -# find_translations_and_store_keys "$translation_file" -# done - echo "Keys saved to $KEYS_FILE" echo "Now go through the list and remove the keys that are used." line_count=$(wc -l < $STYLES_KEYS_FILE) echo "Number of lines in the file: $line_count" -lookfor_unused_keywords +# lookfor_unused_keywords echo "Unused keys are into to $STYLES_KEYS_FILE" line_count2=$(wc -l < $STYLES_KEYS_FILE) echo "Number of lines in the file: $line_count2" -# cat "$STYLES_KEYS_FILE" -show_unused_style_keywords \ No newline at end of file +# show_unused_style_keywords \ No newline at end of file From c04114bf72a44cc795e6b537f9d4b2035c8572e7 Mon Sep 17 00:00:00 2001 From: Eduardo Date: Wed, 6 Sep 2023 17:28:19 +0200 Subject: [PATCH 013/151] getting keys from utilities folder to find unused styles --- scripts/find-unused-keys.sh | 131 +++++++++++++++++++++++++++--------- 1 file changed, 99 insertions(+), 32 deletions(-) diff --git a/scripts/find-unused-keys.sh b/scripts/find-unused-keys.sh index b99e28d3fe8a..8e00c19dbeef 100755 --- a/scripts/find-unused-keys.sh +++ b/scripts/find-unused-keys.sh @@ -3,8 +3,9 @@ # Configurations SRC_DIR="src" STYLES_FILE="src/styles/styles.js" +UTILITIES_STYLES_FILE="src/styles/utilities" STYLES_KEYS_FILE="scripts/style_keys_list_temp.txt" -TRANSLATION_KEYS_FILE="scripts/translations_keys_list_temp.txt" +UTILITY_STYLES_KEYS_FILE="scripts/utility_keys_list_temp.txt" REMOVAL_KEYS_FILE="scripts/removal_keys_list_temp.txt" # Create an empty temp file if it doesn't exist @@ -35,19 +36,22 @@ show_unused_style_keywords() { # Function to remove a keyword from the temp file remove_keyword() { keyword="$1" - - grep -v "$keyword" "$STYLES_KEYS_FILE" > "$REMOVAL_KEYS_FILE" - line_count=$(wc -l < $REMOVAL_KEYS_FILE) - mv "$REMOVAL_KEYS_FILE" "$STYLES_KEYS_FILE" + if grep -q "$keyword" "$STYLES_KEYS_FILE"; then + grep -v "$keyword" "$STYLES_KEYS_FILE" > "$REMOVAL_KEYS_FILE" + mv "$REMOVAL_KEYS_FILE" "$STYLES_KEYS_FILE" + return 0 # Keyword was removed + else + return 1 # Keyword was not found + fi } lookfor_unused_keywords() { # Loop through all files in the src folder find 'src' -type f -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" | while read -r file; do - # Search for keywords starting with "styles" - grep -E -o '\bstyles\.[a-zA-Z0-9_.]*' "$file" | grep -v '\/\/' | grep -vE '\/\*.*\*\/' | while IFS= read -r keyword; do - + # Search for keywords starting with "styles" + grep -E -o '\bstyles\.[a-zA-Z0-9_.]*' "$file" | grep -v '\/\/' | grep -vE '\/\*.*\*\/' | while IFS= read -r keyword; do + # Remove any [ ] characters from the keyword clean_keyword="${keyword//[\[\]]/}" # skip styles. keyword that might be used in comments @@ -55,8 +59,17 @@ lookfor_unused_keywords() { continue fi - remove_keyword "$clean_keyword" + if ! remove_keyword "$clean_keyword" ; then + # In case of a leaf of the styles object is being used, it meas the parent objects is being used + # we need to mark it as used. + if [[ "$clean_keyword" =~ ^styles\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+$ ]]; then + # Keyword has more than two words, remove words after the second word + keyword_prefix="$(echo "$clean_keyword" | sed -E 's/(styles\.[a-zA-Z0-9_-]+)\..*/\1/')" + remove_keyword "$keyword_prefix" + fi + fi done + done } @@ -70,34 +83,30 @@ find_styles_and_store_keys() { while IFS= read -r line; do ((line_number++)) # Increment the line number - # Skip lines that are not key-related - if [[ ! "$line" =~ ^[[:space:]]*const[[:space:]]+([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*\{|^[[:space:]]*([a-zA-Z0-9_-]+\.)?[a-zA-Z0-9_-]+:[[:space:]]*\{|^[[:space:]]*\} ]]; then + # Skip lines that are not key-related + if [[ ! "$line" =~ ^[[:space:]]*(const|let|var)[[:space:]]+([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*\{|^[[:space:]]*([a-zA-Z0-9_-]+\.)?[a-zA-Z0-9_-]+:[[:space:]]*\{|^[[:space:]]*\} && ! "$line" =~ ^[[:space:]]*([a-zA-Z0-9_-])+:[[:space:]]*\(.*\)[[:space:]]*'=>'[[:space:]]*\(\{ ]]; then continue fi - - if [[ "$line" =~ ^[[:space:]]*const[[:space:]]+([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*\{ ]]; then - root_key=$(echo "${BASH_REMATCH[1]}" | sed -E "s/[:[:space:]]*\{.*//") - elif [[ "$line" =~ ^[[:space:]]*([a-zA-Z0-9_-]+\.)?[a-zA-Z0-9_-]+:[[:space:]]*\{ ]]; then + # Handle keys defined in functions within objects + function_key_pattern="^[[:space:]]*([a-zA-Z0-9_-])+:[[:space:]]*\(.*\)[[:space:]]*'=>'[[:space:]]*\(\{" + if [[ "$line" =~ ^[[:space:]]*(const|let|var)[[:space:]]+([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*\{ ]]; then + root_key=$(echo "${BASH_REMATCH[2]}" | sed -E "s/[:[:space:]]*\{.*//") + elif [[ "$line" =~ ^[[:space:]]*([a-zA-Z0-9_-]+\.)?[a-zA-Z0-9_-]+:[[:space:]]*\{ || "$line" =~ ^[[:space:]]*([a-zA-Z0-9_-])+:[[:space:]]*\(.*\)[[:space:]]*'=>'[[:space:]]*\(\{ ]]; then local key=$(echo "$line" | sed -E "s/[:[:space:]]*\{.*//") - # local line_number=$(echo "$line" | grep -n "$key:" | cut -d':' -f1) - echo "line $line" - # Handle keys defined in functions within objects - function_key_pattern="[a-zA-Z0-9_-]+:[[:space:]]*\\(.*\\)[[:space:]]*=>[[:space:]]*\\{" - if [[ "$line" =~ $function_key_pattern ]]; then + + if [[ "$line" =~ ^[[:space:]]*([a-zA-Z0-9_-])+:[[:space:]]*\(.*\)[[:space:]]*'=>'[[:space:]]*\(\{ ]]; then key="${BASH_REMATCH[0]%%:*}" fi + key="${key// /}" # Trim spaces if [[ ${#parent_keys[@]} -gt 0 ]]; then parent_key_trimmed="${parent_keys[${#parent_keys[@]}-1]// /}" # Trim spaces - key_trimmed="${key// /}" # Trim spaces - key="$parent_key_trimmed.$key_trimmed" + key="$parent_key_trimmed.$key" elif [[ -n "$root_key" ]]; then parent_key_trimmed="${root_key// /}" # Trim spaces - key_trimmed="${key// /}" # Trim spaces - key="$parent_key_trimmed.$key_trimmed" + key="$parent_key_trimmed.$key" fi - # echo "$key:$file:$line_number" >> "$STYLES_KEYS_FILE" if [[ "$key" == "styles."* ]]; then echo "$key:$file:$line_number" >> "$STYLES_KEYS_FILE" else @@ -105,27 +114,85 @@ find_styles_and_store_keys() { fi parent_keys+=("$key") elif [[ "$line" =~ ^[[:space:]]*\} ]]; then - # unset "parent_keys[${#parent_keys[@]}-1]" parent_keys=("${parent_keys[@]:0:${#parent_keys[@]}-1}") fi - # done < <(grep -E "^[[:space:]]*const[[:space:]]+([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*\{|^[[:space:]]*([a-zA-Z0-9_-]+\.)?[a-zA-Z0-9_-]+:[[:space:]]*\{|^[[:space:]]*\}" "$file") done < "$file" } +find_utility_styles_store_prefix() { + # Loop through all files in the src folder + find 'src/styles' -type f -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" | while read -r file; do + + # Search for keywords starting with "styles" + grep -E -o './utilities/[a-zA-Z0-9_-]+' "$file" | grep -v '\/\/' | grep -vE '\/\*.*\*\/' | while IFS= read -r keyword; do + variable=$(echo "$keyword" | sed 's/.*\///') + variable_trimmed="${variable// /}" # Trim spaces + + echo "$variable_trimmed" >> "$UTILITY_STYLES_KEYS_FILE" + done + done + + # Sort and remove duplicates from the temporary file + sort -u -o "$UTILITY_STYLES_KEYS_FILE" "$UTILITY_STYLES_KEYS_FILE" +} + +find_utility_usage_as_styles() { + find $UTILITIES_STYLES_FILE -type f -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" | while read -r file; do + if [ -d "$path" ]; then + # Use the folder name as the root key + root_key=$(basename "$path") + echo "styles.$root_key:$path:0" >> "$STYLES_KEYS_FILE" + continue + fi + find_styles_and_store_keys $file + done +} + +lookfor_unused_utilities() { + # Read each utility keyword from the file + while read -r keyword; do + # Creating a copy so later the replacement can reference it + original_keyword="$keyword" + + # Iterate through all files in "src/styles" + find 'src/styles' -type f -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" | while read -r file; do + # Find all words that match "$keyword.[a-zA-Z0-9_-]+" + grep -E -o "$original_keyword\.[a-zA-Z0-9_-]+" "$file" | grep -v '\/\/' | grep -vE '\/\*.*\*\/' | while IFS= read -r match; do + # Replace the utility prefix with "styles" + variable=$(echo "$match" | sed "s/^$original_keyword/styles/") + + # Call the remove_keyword function with the variable + remove_keyword "$variable" + done + done + done < "$UTILITY_STYLES_KEYS_FILE" +} + +# Find and store the name of the utility files as keys +find_utility_styles_store_prefix + # Find and store keys from styles.js find_styles_and_store_keys "$STYLES_FILE" +find_utility_usage_as_styles + +# Look for usages of utilities into src/styles +lookfor_unused_utilities + echo "Keys saved to $KEYS_FILE" -echo "Now go through the list and remove the keys that are used." +echo "Now going through the list and removing the keys that are being used." line_count=$(wc -l < $STYLES_KEYS_FILE) -echo "Number of lines in the file: $line_count" +echo "Number of styles found: $line_count" -# lookfor_unused_keywords +lookfor_unused_keywords echo "Unused keys are into to $STYLES_KEYS_FILE" line_count2=$(wc -l < $STYLES_KEYS_FILE) -echo "Number of lines in the file: $line_count2" +echo "Number of styles not being used: $line_count2" + +show_unused_style_keywords -# show_unused_style_keywords \ No newline at end of file +# Delete all files containing a specific pattern in their name +find /scripts -name "*keys_list_temp*" -type f -exec rm -f {} \; \ No newline at end of file From c2ea419b30a93e0a1fa28442dcb6717ed1816744 Mon Sep 17 00:00:00 2001 From: Eduardo Date: Wed, 6 Sep 2023 18:29:24 +0200 Subject: [PATCH 014/151] Code clean up + improvements to the output --- scripts/find-unused-keys.sh | 42 ++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/scripts/find-unused-keys.sh b/scripts/find-unused-keys.sh index 8e00c19dbeef..0f713a312197 100755 --- a/scripts/find-unused-keys.sh +++ b/scripts/find-unused-keys.sh @@ -7,30 +7,43 @@ UTILITIES_STYLES_FILE="src/styles/utilities" STYLES_KEYS_FILE="scripts/style_keys_list_temp.txt" UTILITY_STYLES_KEYS_FILE="scripts/utility_keys_list_temp.txt" REMOVAL_KEYS_FILE="scripts/removal_keys_list_temp.txt" + +# FILE_EXTENSIONS="-name '*.js' -o -name '*.jsx' -o -name '*.ts' -o -name '*.tsx'" +FILE_EXTENSIONS=('-name' '*.js' '-o' '-name' '*.jsx' '-o' '-name' '*.ts' '-o' '-name' '*.tsx') + +# trap ctrl-c and call ctrl_c() +trap ctrl_c INT + +function ctrl_c() { + find scripts -name "*keys_list_temp*" -type f -exec rm -f {} \; + exit 1 +} +source scripts/shellUtils.sh + # Create an empty temp file if it doesn't exist -if [ ! -f "$REMOVAL_KEYS_FILE" ]; then - touch "$REMOVAL_KEYS_FILE" -fi +# if [ ! -f "$REMOVAL_KEYS_FILE" ]; then +# touch "$REMOVAL_KEYS_FILE" +# fi # Read the style file with unused keys show_unused_style_keywords() { while IFS=: read -r key file line_number; do - line_count=$(wc -l < $STYLES_KEYS_FILE) - echo "Unused keys: $line_count" - echo "File: $file" + title "File: $file:$line_number" # Get lines before and after the error line lines_before=$((line_number - 3)) lines_after=$((line_number + 3)) # Print context of the error line - echo "Context around line $line_number:" sed -n "$lines_before,$lines_after p" "$file" | awk -v key="$key" '{gsub(key, "\033[1;31m"key"\033[0m"); print}' - echo "Unused key: $key" + error "Unused key: $key" echo "--------------------------------" done < "$STYLES_KEYS_FILE" + + line_count=$(wc -l < $STYLES_KEYS_FILE) + error "Unused keys: $line_count" } # Function to remove a keyword from the temp file @@ -39,6 +52,7 @@ remove_keyword() { if grep -q "$keyword" "$STYLES_KEYS_FILE"; then grep -v "$keyword" "$STYLES_KEYS_FILE" > "$REMOVAL_KEYS_FILE" mv "$REMOVAL_KEYS_FILE" "$STYLES_KEYS_FILE" + return 0 # Keyword was removed else return 1 # Keyword was not found @@ -47,7 +61,7 @@ remove_keyword() { lookfor_unused_keywords() { # Loop through all files in the src folder - find 'src' -type f -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" | while read -r file; do + find 'src' -type f \( "${FILE_EXTENSIONS[@]}" \) | while read -r file; do # Search for keywords starting with "styles" grep -E -o '\bstyles\.[a-zA-Z0-9_.]*' "$file" | grep -v '\/\/' | grep -vE '\/\*.*\*\/' | while IFS= read -r keyword; do @@ -121,7 +135,7 @@ find_styles_and_store_keys() { find_utility_styles_store_prefix() { # Loop through all files in the src folder - find 'src/styles' -type f -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" | while read -r file; do + find 'src/styles' -type f \( "${FILE_EXTENSIONS[@]}" \) | while read -r file; do # Search for keywords starting with "styles" grep -E -o './utilities/[a-zA-Z0-9_-]+' "$file" | grep -v '\/\/' | grep -vE '\/\*.*\*\/' | while IFS= read -r keyword; do @@ -137,7 +151,7 @@ find_utility_styles_store_prefix() { } find_utility_usage_as_styles() { - find $UTILITIES_STYLES_FILE -type f -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" | while read -r file; do + find $UTILITIES_STYLES_FILE -type f \( "${FILE_EXTENSIONS[@]}" \) | while read -r file; do if [ -d "$path" ]; then # Use the folder name as the root key root_key=$(basename "$path") @@ -155,7 +169,7 @@ lookfor_unused_utilities() { original_keyword="$keyword" # Iterate through all files in "src/styles" - find 'src/styles' -type f -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" | while read -r file; do + find 'src/styles' -type f \( "${FILE_EXTENSIONS[@]}" \) | while read -r file; do # Find all words that match "$keyword.[a-zA-Z0-9_-]+" grep -E -o "$original_keyword\.[a-zA-Z0-9_-]+" "$file" | grep -v '\/\/' | grep -vE '\/\*.*\*\/' | while IFS= read -r match; do # Replace the utility prefix with "styles" @@ -194,5 +208,5 @@ echo "Number of styles not being used: $line_count2" show_unused_style_keywords -# Delete all files containing a specific pattern in their name -find /scripts -name "*keys_list_temp*" -type f -exec rm -f {} \; \ No newline at end of file +# Delete all temo files +find scripts -name "*keys_list_temp*" -type f -exec rm -f {} \; \ No newline at end of file From 3042c9abe0fa3f3bab4bb0fb97a1c8c039f748c7 Mon Sep 17 00:00:00 2001 From: Eduardo Date: Thu, 7 Sep 2023 16:15:53 +0200 Subject: [PATCH 015/151] Clean up, used builtins and improved code readability --- scripts/find-unused-keys.sh | 165 +++++++++++++++++++----------------- 1 file changed, 88 insertions(+), 77 deletions(-) diff --git a/scripts/find-unused-keys.sh b/scripts/find-unused-keys.sh index 0f713a312197..b24b4c1150e2 100755 --- a/scripts/find-unused-keys.sh +++ b/scripts/find-unused-keys.sh @@ -1,30 +1,41 @@ #!/bin/bash # Configurations -SRC_DIR="src" -STYLES_FILE="src/styles/styles.js" -UTILITIES_STYLES_FILE="src/styles/utilities" -STYLES_KEYS_FILE="scripts/style_keys_list_temp.txt" -UTILITY_STYLES_KEYS_FILE="scripts/utility_keys_list_temp.txt" -REMOVAL_KEYS_FILE="scripts/removal_keys_list_temp.txt" +readonly SRC_DIR="src" +readonly STYLES_FILE="src/styles/styles.js" +readonly UTILITIES_STYLES_FILE="src/styles/utilities" +readonly STYLES_KEYS_FILE="scripts/style_keys_list_temp.txt" +readonly UTILITY_STYLES_KEYS_FILE="scripts/utility_keys_list_temp.txt" +readonly REMOVAL_KEYS_FILE="scripts/removal_keys_list_temp.txt" +readonly AMOUNT_LINES_TO_SHOW=3 + +readonly FILE_EXTENSIONS=('-name' '*.js' '-o' '-name' '*.jsx' '-o' '-name' '*.ts' '-o' '-name' '*.tsx') + +# Regex +readonly OBJ_PROP_DECLARATION_REGEX="^[[:space:]]*(const|let|var)[[:space:]]+([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*\{|^[[:space:]]*([a-zA-Z0-9_-]+\.)?[a-zA-Z0-9_-]+:[[:space:]]*\{|^[[:space:]]*\}" +readonly OBJ_PROP_FUNC_DEFINITION_REGEX="^[[:space:]]*(const|let|var)[[:space:]]+([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*\{|^[[:space:]]*([a-zA-Z0-9_-]+\.)?[a-zA-Z0-9_-]+:[[:space:]]*\{|^[[:space:]]*\} && ! [[:space:]]*([a-zA-Z0-9_-])+:[[:space:]]*\(.*\)[[:space:]]*'=>'[[:space:]]*\(\{" +readonly OBJ_DEFINITION_REGEX="^[[:space:]]*(const|let|var)[[:space:]]+([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*\{" +readonly CAPTURE_ARROW_FUNC_REGEX='^[[:space:]]*([a-zA-Z0-9_-])+:[[:space:]]*\(.*\)[[:space:]]*'=>'[[:space:]]*\(\{' +readonly CAPTURE_OBJ_ARROW_FUNC_REGEX='^[[:space:]]*([a-zA-Z0-9_-]+\.)?[a-zA-Z0-9_-]+:[[:space:]]*\{|^[[:space:]]*([a-zA-Z0-9_-])+:[[:space:]]*\(.*\)[[:space:]]*'=>'[[:space:]]*\(\{' -# FILE_EXTENSIONS="-name '*.js' -o -name '*.jsx' -o -name '*.ts' -o -name '*.tsx'" -FILE_EXTENSIONS=('-name' '*.js' '-o' '-name' '*.jsx' '-o' '-name' '*.ts' '-o' '-name' '*.tsx') +source scripts/shellUtils.sh # trap ctrl-c and call ctrl_c() trap ctrl_c INT -function ctrl_c() { +delete_temp_files() { find scripts -name "*keys_list_temp*" -type f -exec rm -f {} \; +} + +ctrl_c() { + delete_temp_files exit 1 } -source scripts/shellUtils.sh - -# Create an empty temp file if it doesn't exist -# if [ ! -f "$REMOVAL_KEYS_FILE" ]; then -# touch "$REMOVAL_KEYS_FILE" -# fi +count_lines() { + local file=$1 + wc -l < $file +} # Read the style file with unused keys show_unused_style_keywords() { @@ -32,23 +43,29 @@ show_unused_style_keywords() { title "File: $file:$line_number" # Get lines before and after the error line - lines_before=$((line_number - 3)) - lines_after=$((line_number + 3)) - - # Print context of the error line - sed -n "$lines_before,$lines_after p" "$file" | awk -v key="$key" '{gsub(key, "\033[1;31m"key"\033[0m"); print}' + local lines_before=$((line_number - AMOUNT_LINES_TO_SHOW)) + local lines_after=$((line_number + AMOUNT_LINES_TO_SHOW)) + # Read the lines into an array + local lines=() + while IFS= read -r line; do + lines+=("$line") + done < "$file" + + # Loop through the lines + for ((i = lines_before; i <= lines_after; i++)); do + local line="${lines[i]}" + # Print context of the error line + echo "$line" + done error "Unused key: $key" echo "--------------------------------" done < "$STYLES_KEYS_FILE" - - line_count=$(wc -l < $STYLES_KEYS_FILE) - error "Unused keys: $line_count" } # Function to remove a keyword from the temp file remove_keyword() { - keyword="$1" + local keyword="$1" if grep -q "$keyword" "$STYLES_KEYS_FILE"; then grep -v "$keyword" "$STYLES_KEYS_FILE" > "$REMOVAL_KEYS_FILE" mv "$REMOVAL_KEYS_FILE" "$STYLES_KEYS_FILE" @@ -61,13 +78,13 @@ remove_keyword() { lookfor_unused_keywords() { # Loop through all files in the src folder - find 'src' -type f \( "${FILE_EXTENSIONS[@]}" \) | while read -r file; do + while read -r file; do # Search for keywords starting with "styles" - grep -E -o '\bstyles\.[a-zA-Z0-9_.]*' "$file" | grep -v '\/\/' | grep -vE '\/\*.*\*\/' | while IFS= read -r keyword; do + while IFS= read -r keyword; do # Remove any [ ] characters from the keyword - clean_keyword="${keyword//[\[\]]/}" + local clean_keyword="${keyword//[\[\]]/}" # skip styles. keyword that might be used in comments if [[ "$clean_keyword" == "styles." ]]; then continue @@ -78,46 +95,41 @@ lookfor_unused_keywords() { # we need to mark it as used. if [[ "$clean_keyword" =~ ^styles\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+$ ]]; then # Keyword has more than two words, remove words after the second word - keyword_prefix="$(echo "$clean_keyword" | sed -E 's/(styles\.[a-zA-Z0-9_-]+)\..*/\1/')" + local keyword_prefix="${clean_keyword%.*}" remove_keyword "$keyword_prefix" fi fi - done - - done + done < <(grep -E -o '\bstyles\.[a-zA-Z0-9_.]*' "$file" | grep -v '\/\/' | grep -vE '\/\*.*\*\/') + done < <(find 'src' -type f \( "${FILE_EXTENSIONS[@]}" \)) } + # Function to find and store keys from a file find_styles_and_store_keys() { local file="$1" local parent_keys=() local root_key="" - local line_number=0 # Initialize the line number + local line_number=0 while IFS= read -r line; do - ((line_number++)) # Increment the line number + ((line_number++)) # Skip lines that are not key-related - if [[ ! "$line" =~ ^[[:space:]]*(const|let|var)[[:space:]]+([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*\{|^[[:space:]]*([a-zA-Z0-9_-]+\.)?[a-zA-Z0-9_-]+:[[:space:]]*\{|^[[:space:]]*\} && ! "$line" =~ ^[[:space:]]*([a-zA-Z0-9_-])+:[[:space:]]*\(.*\)[[:space:]]*'=>'[[:space:]]*\(\{ ]]; then + if [[ ! "$line" =~ $OBJ_PROP_DECLARATION_REGEX && ! "$line" =~ $CAPTURE_ARROW_FUNC_REGEX ]]; then continue fi - # Handle keys defined in functions within objects - function_key_pattern="^[[:space:]]*([a-zA-Z0-9_-])+:[[:space:]]*\(.*\)[[:space:]]*'=>'[[:space:]]*\(\{" - if [[ "$line" =~ ^[[:space:]]*(const|let|var)[[:space:]]+([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*\{ ]]; then - root_key=$(echo "${BASH_REMATCH[2]}" | sed -E "s/[:[:space:]]*\{.*//") - elif [[ "$line" =~ ^[[:space:]]*([a-zA-Z0-9_-]+\.)?[a-zA-Z0-9_-]+:[[:space:]]*\{ || "$line" =~ ^[[:space:]]*([a-zA-Z0-9_-])+:[[:space:]]*\(.*\)[[:space:]]*'=>'[[:space:]]*\(\{ ]]; then - local key=$(echo "$line" | sed -E "s/[:[:space:]]*\{.*//") - - if [[ "$line" =~ ^[[:space:]]*([a-zA-Z0-9_-])+:[[:space:]]*\(.*\)[[:space:]]*'=>'[[:space:]]*\(\{ ]]; then - key="${BASH_REMATCH[0]%%:*}" - fi - + + if [[ "$line" =~ $OBJ_DEFINITION_REGEX ]]; then + root_key="${BASH_REMATCH[2]%%:*{*)}" + elif [[ "$line" =~ $CAPTURE_OBJ_ARROW_FUNC_REGEX ]]; then + # Removing all the extra lines after the ":" + local key="${line%%:*}" key="${key// /}" # Trim spaces if [[ ${#parent_keys[@]} -gt 0 ]]; then - parent_key_trimmed="${parent_keys[${#parent_keys[@]}-1]// /}" # Trim spaces + local parent_key_trimmed="${parent_keys[${#parent_keys[@]}-1]// /}" # Trim spaces key="$parent_key_trimmed.$key" elif [[ -n "$root_key" ]]; then - parent_key_trimmed="${root_key// /}" # Trim spaces + local parent_key_trimmed="${root_key// /}" # Trim spaces key="$parent_key_trimmed.$key" fi @@ -135,50 +147,49 @@ find_styles_and_store_keys() { find_utility_styles_store_prefix() { # Loop through all files in the src folder - find 'src/styles' -type f \( "${FILE_EXTENSIONS[@]}" \) | while read -r file; do - + while read -r file; do # Search for keywords starting with "styles" - grep -E -o './utilities/[a-zA-Z0-9_-]+' "$file" | grep -v '\/\/' | grep -vE '\/\*.*\*\/' | while IFS= read -r keyword; do - variable=$(echo "$keyword" | sed 's/.*\///') - variable_trimmed="${variable// /}" # Trim spaces + while IFS= read -r keyword; do + local variable="${keyword##*/}" + local variable_trimmed="${variable// /}" # Trim spaces echo "$variable_trimmed" >> "$UTILITY_STYLES_KEYS_FILE" - done - done + done < <(grep -E -o './utilities/[a-zA-Z0-9_-]+' "$file" | grep -v '\/\/' | grep -vE '\/\*.*\*\/') + done < <(find 'src/styles' -type f \( "${FILE_EXTENSIONS[@]}" \)) # Sort and remove duplicates from the temporary file sort -u -o "$UTILITY_STYLES_KEYS_FILE" "$UTILITY_STYLES_KEYS_FILE" } find_utility_usage_as_styles() { - find $UTILITIES_STYLES_FILE -type f \( "${FILE_EXTENSIONS[@]}" \) | while read -r file; do + while read -r file; do if [ -d "$path" ]; then # Use the folder name as the root key - root_key=$(basename "$path") + local root_key=$(basename "$path") echo "styles.$root_key:$path:0" >> "$STYLES_KEYS_FILE" continue fi find_styles_and_store_keys $file - done + done < <(find $UTILITIES_STYLES_FILE -type f \( "${FILE_EXTENSIONS[@]}" \)) } lookfor_unused_utilities() { # Read each utility keyword from the file while read -r keyword; do # Creating a copy so later the replacement can reference it - original_keyword="$keyword" + local original_keyword="$keyword" # Iterate through all files in "src/styles" - find 'src/styles' -type f \( "${FILE_EXTENSIONS[@]}" \) | while read -r file; do + while read -r file; do # Find all words that match "$keyword.[a-zA-Z0-9_-]+" - grep -E -o "$original_keyword\.[a-zA-Z0-9_-]+" "$file" | grep -v '\/\/' | grep -vE '\/\*.*\*\/' | while IFS= read -r match; do + while IFS= read -r match; do # Replace the utility prefix with "styles" - variable=$(echo "$match" | sed "s/^$original_keyword/styles/") + local variable="${match/#$original_keyword/styles}" # Call the remove_keyword function with the variable remove_keyword "$variable" - done - done + done < <(grep -E -o "$original_keyword\.[a-zA-Z0-9_-]+" "$file" | grep -v '\/\/' | grep -vE '\/\*.*\*\/') + done < <(find 'src/styles' -type f \( "${FILE_EXTENSIONS[@]}" \)) done < "$UTILITY_STYLES_KEYS_FILE" } @@ -193,20 +204,20 @@ find_utility_usage_as_styles # Look for usages of utilities into src/styles lookfor_unused_utilities -echo "Keys saved to $KEYS_FILE" -echo "Now going through the list and removing the keys that are being used." - -line_count=$(wc -l < $STYLES_KEYS_FILE) -echo "Number of styles found: $line_count" +echo "⏱️ Now going through the list and looking for unused keys." lookfor_unused_keywords -echo "Unused keys are into to $STYLES_KEYS_FILE" - -line_count2=$(wc -l < $STYLES_KEYS_FILE) -echo "Number of styles not being used: $line_count2" - -show_unused_style_keywords - -# Delete all temo files -find scripts -name "*keys_list_temp*" -type f -exec rm -f {} \; \ No newline at end of file +final_styles_line_count=$(count_lines "$STYLES_KEYS_FILE") + +if [[ $final_styles_line_count -eq 0 ]]; then + # Exit successfully (status code 0) + delete_temp_files + success "Styles are in a good shape" + exit 0 +else + show_unused_style_keywords + delete_temp_files + error "Unused keys: $final_styles_line_count" + exit 1 +fi \ No newline at end of file From 6fa51d0bbbfe9b244a7c7732b8b9ac3f1c8d835e Mon Sep 17 00:00:00 2001 From: Eduardo Date: Thu, 7 Sep 2023 18:09:34 +0200 Subject: [PATCH 016/151] Fixed lint issues --- scripts/find-unused-keys.sh | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/scripts/find-unused-keys.sh b/scripts/find-unused-keys.sh index b24b4c1150e2..568e3f7045d2 100755 --- a/scripts/find-unused-keys.sh +++ b/scripts/find-unused-keys.sh @@ -13,10 +13,9 @@ readonly FILE_EXTENSIONS=('-name' '*.js' '-o' '-name' '*.jsx' '-o' '-name' '*.ts # Regex readonly OBJ_PROP_DECLARATION_REGEX="^[[:space:]]*(const|let|var)[[:space:]]+([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*\{|^[[:space:]]*([a-zA-Z0-9_-]+\.)?[a-zA-Z0-9_-]+:[[:space:]]*\{|^[[:space:]]*\}" -readonly OBJ_PROP_FUNC_DEFINITION_REGEX="^[[:space:]]*(const|let|var)[[:space:]]+([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*\{|^[[:space:]]*([a-zA-Z0-9_-]+\.)?[a-zA-Z0-9_-]+:[[:space:]]*\{|^[[:space:]]*\} && ! [[:space:]]*([a-zA-Z0-9_-])+:[[:space:]]*\(.*\)[[:space:]]*'=>'[[:space:]]*\(\{" readonly OBJ_DEFINITION_REGEX="^[[:space:]]*(const|let|var)[[:space:]]+([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*\{" -readonly CAPTURE_ARROW_FUNC_REGEX='^[[:space:]]*([a-zA-Z0-9_-])+:[[:space:]]*\(.*\)[[:space:]]*'=>'[[:space:]]*\(\{' -readonly CAPTURE_OBJ_ARROW_FUNC_REGEX='^[[:space:]]*([a-zA-Z0-9_-]+\.)?[a-zA-Z0-9_-]+:[[:space:]]*\{|^[[:space:]]*([a-zA-Z0-9_-])+:[[:space:]]*\(.*\)[[:space:]]*'=>'[[:space:]]*\(\{' +readonly CAPTURE_ARROW_FUNC_REGEX="^[[:space:]]*([a-zA-Z0-9_-])+:[[:space:]]*\(.*\)[[:space:]]*'=>'[[:space:]]*\(\{" +readonly CAPTURE_OBJ_ARROW_FUNC_REGEX="^[[:space:]]*([a-zA-Z0-9_-]+\.)?[a-zA-Z0-9_-]+:[[:space:]]*\{|^[[:space:]]*([a-zA-Z0-9_-])+:[[:space:]]*\(.*\)[[:space:]]*'=>'[[:space:]]*\(\{" source scripts/shellUtils.sh @@ -27,6 +26,7 @@ delete_temp_files() { find scripts -name "*keys_list_temp*" -type f -exec rm -f {} \; } +# shellcheck disable=SC2317 # Don't warn about unreachable commands in this function ctrl_c() { delete_temp_files exit 1 @@ -34,7 +34,7 @@ ctrl_c() { count_lines() { local file=$1 - wc -l < $file + wc -l < "$file" } # Read the style file with unused keys @@ -100,7 +100,7 @@ lookfor_unused_keywords() { fi fi done < <(grep -E -o '\bstyles\.[a-zA-Z0-9_.]*' "$file" | grep -v '\/\/' | grep -vE '\/\*.*\*\/') - done < <(find 'src' -type f \( "${FILE_EXTENSIONS[@]}" \)) + done < <(find $SRC_DIR -type f \( "${FILE_EXTENSIONS[@]}" \)) } @@ -163,13 +163,13 @@ find_utility_styles_store_prefix() { find_utility_usage_as_styles() { while read -r file; do - if [ -d "$path" ]; then + if [ -d "$file" ]; then # Use the folder name as the root key - local root_key=$(basename "$path") + local root_key=$(basename "$file") echo "styles.$root_key:$path:0" >> "$STYLES_KEYS_FILE" continue fi - find_styles_and_store_keys $file + find_styles_and_store_keys "$file" done < <(find $UTILITIES_STYLES_FILE -type f \( "${FILE_EXTENSIONS[@]}" \)) } From 3424140f511f65020540ce5d7ca88b5ca49ce50f Mon Sep 17 00:00:00 2001 From: Eduardo Date: Thu, 7 Sep 2023 18:12:11 +0200 Subject: [PATCH 017/151] typo fixed --- scripts/find-unused-keys.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/find-unused-keys.sh b/scripts/find-unused-keys.sh index 568e3f7045d2..f492f43a5233 100755 --- a/scripts/find-unused-keys.sh +++ b/scripts/find-unused-keys.sh @@ -91,7 +91,7 @@ lookfor_unused_keywords() { fi if ! remove_keyword "$clean_keyword" ; then - # In case of a leaf of the styles object is being used, it meas the parent objects is being used + # In case of a leaf of the styles object is being used, it means the parent objects is being used # we need to mark it as used. if [[ "$clean_keyword" =~ ^styles\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+$ ]]; then # Keyword has more than two words, remove words after the second word From 656beff46a4b2f42bd8b2cf88603f6fbd9156aac Mon Sep 17 00:00:00 2001 From: Jasper Huang Date: Thu, 7 Sep 2023 12:32:13 -0700 Subject: [PATCH 018/151] update policy type definitions --- src/types/onyx/Policy.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index cacbb5d15199..87382090f294 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -32,6 +32,10 @@ type Policy = { isFromFullPolicy?: boolean; lastModified?: string; customUnits?: Record; + + areChatRoomsEnabled?: boolean; + + isPolicyExpenseChatEnabled?: boolean; }; export default Policy; From b43deaf73b193d3c57cc92c76698eda54821567a Mon Sep 17 00:00:00 2001 From: Jasper Huang Date: Thu, 7 Sep 2023 12:43:02 -0700 Subject: [PATCH 019/151] add and use areChatRoomsEnabled --- src/libs/PolicyUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/PolicyUtils.js b/src/libs/PolicyUtils.js index 164f284a4ef5..743dfba34382 100644 --- a/src/libs/PolicyUtils.js +++ b/src/libs/PolicyUtils.js @@ -10,7 +10,7 @@ import ONYXKEYS from '../ONYXKEYS'; * @returns {Array} */ function getActivePolicies(policies) { - return _.filter(policies, (policy) => policy && policy.isPolicyExpenseChatEnabled && policy.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE); + return _.filter(policies, (policy) => policy && (policy.isPolicyExpenseChatEnabled || policy.areChatRoomsEnabled) && policy.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE); } /** From ac8aea27d87efac1766ac5fc552d9c798736942a Mon Sep 17 00:00:00 2001 From: Eduardo Date: Fri, 8 Sep 2023 11:59:01 +0200 Subject: [PATCH 020/151] fixed some lint errors + clean ups --- scripts/find-unused-keys.sh | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/scripts/find-unused-keys.sh b/scripts/find-unused-keys.sh index f492f43a5233..2bb0c8200aa0 100755 --- a/scripts/find-unused-keys.sh +++ b/scripts/find-unused-keys.sh @@ -107,6 +107,7 @@ lookfor_unused_keywords() { # Function to find and store keys from a file find_styles_and_store_keys() { local file="$1" + local base_name="${2:-styles}" # Set styles as default local parent_keys=() local root_key="" local line_number=0 @@ -134,9 +135,9 @@ find_styles_and_store_keys() { fi if [[ "$key" == "styles."* ]]; then - echo "$key:$file:$line_number" >> "$STYLES_KEYS_FILE" + echo "${key}:${file}:${line_number}" >> "$STYLES_KEYS_FILE" else - echo "styles.$key:$file:$line_number" >> "$STYLES_KEYS_FILE" + echo "styles.${key}|${base_name}.${key}:${file}:${line_number}" >> "$STYLES_KEYS_FILE" fi parent_keys+=("$key") elif [[ "$line" =~ ^[[:space:]]*\} ]]; then @@ -158,18 +159,24 @@ find_utility_styles_store_prefix() { done < <(find 'src/styles' -type f \( "${FILE_EXTENSIONS[@]}" \)) # Sort and remove duplicates from the temporary file - sort -u -o "$UTILITY_STYLES_KEYS_FILE" "$UTILITY_STYLES_KEYS_FILE" + sort -u -o "${UTILITY_STYLES_KEYS_FILE}" "${UTILITY_STYLES_KEYS_FILE}" } find_utility_usage_as_styles() { while read -r file; do - if [ -d "$file" ]; then - # Use the folder name as the root key - local root_key=$(basename "$file") - echo "styles.$root_key:$path:0" >> "$STYLES_KEYS_FILE" + local folder_name + local root_key + local parent_dir + + # Get the folder name, given this utility files are index.js + parent_dir=$(dirname "$file") + root_key=$(basename "${parent_dir}") + + if [[ "${root_key}" == "utilities" ]]; then continue fi - find_styles_and_store_keys "$file" + + find_styles_and_store_keys "${file}" "${root_key}" done < <(find $UTILITIES_STYLES_FILE -type f \( "${FILE_EXTENSIONS[@]}" \)) } @@ -177,7 +184,7 @@ lookfor_unused_utilities() { # Read each utility keyword from the file while read -r keyword; do # Creating a copy so later the replacement can reference it - local original_keyword="$keyword" + local original_keyword="${keyword}" # Iterate through all files in "src/styles" while read -r file; do @@ -185,9 +192,9 @@ lookfor_unused_utilities() { while IFS= read -r match; do # Replace the utility prefix with "styles" local variable="${match/#$original_keyword/styles}" - # Call the remove_keyword function with the variable - remove_keyword "$variable" + remove_keyword "${variable}" + remove_keyword "${match}" done < <(grep -E -o "$original_keyword\.[a-zA-Z0-9_-]+" "$file" | grep -v '\/\/' | grep -vE '\/\*.*\*\/') done < <(find 'src/styles' -type f \( "${FILE_EXTENSIONS[@]}" \)) done < "$UTILITY_STYLES_KEYS_FILE" From ff2174b06790a1f974d14b1037b35005403152d0 Mon Sep 17 00:00:00 2001 From: Eduardo Date: Fri, 8 Sep 2023 15:25:29 +0200 Subject: [PATCH 021/151] removed unused variable --- scripts/find-unused-keys.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/find-unused-keys.sh b/scripts/find-unused-keys.sh index 2bb0c8200aa0..5dff65571d54 100755 --- a/scripts/find-unused-keys.sh +++ b/scripts/find-unused-keys.sh @@ -164,7 +164,6 @@ find_utility_styles_store_prefix() { find_utility_usage_as_styles() { while read -r file; do - local folder_name local root_key local parent_dir From 261adb0db8db5ea80b67f78b060738380b72d5eb Mon Sep 17 00:00:00 2001 From: Eduardo Date: Mon, 11 Sep 2023 16:18:36 +0200 Subject: [PATCH 022/151] moved unused style script into workflow actions --- .../scripts/findUnusedKeys.sh | 22 +++++++++++-------- .github/workflows/findUnusedStyles.yml | 22 +++++++++++++++++++ package.json | 2 +- 3 files changed, 36 insertions(+), 10 deletions(-) rename scripts/find-unused-keys.sh => .github/scripts/findUnusedKeys.sh (90%) create mode 100644 .github/workflows/findUnusedStyles.yml diff --git a/scripts/find-unused-keys.sh b/.github/scripts/findUnusedKeys.sh similarity index 90% rename from scripts/find-unused-keys.sh rename to .github/scripts/findUnusedKeys.sh index 5dff65571d54..1ab00662dfe7 100755 --- a/scripts/find-unused-keys.sh +++ b/.github/scripts/findUnusedKeys.sh @@ -1,12 +1,16 @@ #!/bin/bash # Configurations -readonly SRC_DIR="src" -readonly STYLES_FILE="src/styles/styles.js" -readonly UTILITIES_STYLES_FILE="src/styles/utilities" -readonly STYLES_KEYS_FILE="scripts/style_keys_list_temp.txt" -readonly UTILITY_STYLES_KEYS_FILE="scripts/utility_keys_list_temp.txt" -readonly REMOVAL_KEYS_FILE="scripts/removal_keys_list_temp.txt" +declare LIB_PATH +LIB_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && cd ../../ && pwd)" + +readonly SRC_DIR="${LIB_PATH}/src" +readonly STYLES_DIR="${LIB_PATH}/src/styles" +readonly STYLES_FILE="${LIB_PATH}/src/styles/styles.js" +readonly UTILITIES_STYLES_FILE="${LIB_PATH}/src/styles/utilities" +readonly STYLES_KEYS_FILE="${LIB_PATH}/scripts/style_keys_list_temp.txt" +readonly UTILITY_STYLES_KEYS_FILE="${LIB_PATH}/scripts/utility_keys_list_temp.txt" +readonly REMOVAL_KEYS_FILE="${LIB_PATH}/scripts/removal_keys_list_temp.txt" readonly AMOUNT_LINES_TO_SHOW=3 readonly FILE_EXTENSIONS=('-name' '*.js' '-o' '-name' '*.jsx' '-o' '-name' '*.ts' '-o' '-name' '*.tsx') @@ -23,7 +27,7 @@ source scripts/shellUtils.sh trap ctrl_c INT delete_temp_files() { - find scripts -name "*keys_list_temp*" -type f -exec rm -f {} \; + find "${LIB_PATH}/scripts" -name "*keys_list_temp*" -type f -exec rm -f {} \; } # shellcheck disable=SC2317 # Don't warn about unreachable commands in this function @@ -156,7 +160,7 @@ find_utility_styles_store_prefix() { echo "$variable_trimmed" >> "$UTILITY_STYLES_KEYS_FILE" done < <(grep -E -o './utilities/[a-zA-Z0-9_-]+' "$file" | grep -v '\/\/' | grep -vE '\/\*.*\*\/') - done < <(find 'src/styles' -type f \( "${FILE_EXTENSIONS[@]}" \)) + done < <(find $STYLES_DIR -type f \( "${FILE_EXTENSIONS[@]}" \)) # Sort and remove duplicates from the temporary file sort -u -o "${UTILITY_STYLES_KEYS_FILE}" "${UTILITY_STYLES_KEYS_FILE}" @@ -195,7 +199,7 @@ lookfor_unused_utilities() { remove_keyword "${variable}" remove_keyword "${match}" done < <(grep -E -o "$original_keyword\.[a-zA-Z0-9_-]+" "$file" | grep -v '\/\/' | grep -vE '\/\*.*\*\/') - done < <(find 'src/styles' -type f \( "${FILE_EXTENSIONS[@]}" \)) + done < <(find $STYLES_DIR -type f \( "${FILE_EXTENSIONS[@]}" \)) done < "$UTILITY_STYLES_KEYS_FILE" } diff --git a/.github/workflows/findUnusedStyles.yml b/.github/workflows/findUnusedStyles.yml new file mode 100644 index 000000000000..b832084dcc7e --- /dev/null +++ b/.github/workflows/findUnusedStyles.yml @@ -0,0 +1,22 @@ +name: Find Unused styles + +on: + pull_request: + types: [opened, synchronize] + branches-ignore: [staging, production] + +jobs: + perf-tests: + if: ${{ github.actor != 'OSBotify' }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup NodeJS + uses: Expensify/App/.github/actions/composite/setupNode@main + + - name: Run unused style searcher + shell: bash + run: ./.github/scripts/findUnusedKeys.sh + diff --git a/package.json b/package.json index 9baff5b8f0b1..391e32f40029 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "symbolicate:android": "npx metro-symbolicate android/app/build/generated/sourcemaps/react/release/index.android.bundle.map", "symbolicate:ios": "npx metro-symbolicate main.jsbundle.map", "test:e2e": "node tests/e2e/testRunner.js --development", - "find-missing-keys": "scripts/find-unused-keys.sh" + "gh-actions-unused-styles": "./.github/scripts/findUnusedKeys.sh" }, "dependencies": { "@expensify/react-native-web": "0.18.15", From 82cf6262896e84c37ee761e47e25b94c6632373a Mon Sep 17 00:00:00 2001 From: Eduardo Date: Mon, 11 Sep 2023 16:30:37 +0200 Subject: [PATCH 023/151] added find unused styles into lint.yml workflow --- .github/workflows/findUnusedStyles.yml | 22 ---------------------- .github/workflows/lint.yml | 4 ++++ 2 files changed, 4 insertions(+), 22 deletions(-) delete mode 100644 .github/workflows/findUnusedStyles.yml diff --git a/.github/workflows/findUnusedStyles.yml b/.github/workflows/findUnusedStyles.yml deleted file mode 100644 index b832084dcc7e..000000000000 --- a/.github/workflows/findUnusedStyles.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Find Unused styles - -on: - pull_request: - types: [opened, synchronize] - branches-ignore: [staging, production] - -jobs: - perf-tests: - if: ${{ github.actor != 'OSBotify' }} - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Setup NodeJS - uses: Expensify/App/.github/actions/composite/setupNode@main - - - name: Run unused style searcher - shell: bash - run: ./.github/scripts/findUnusedKeys.sh - diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 795271cab60a..7158d4f67e7b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -31,3 +31,7 @@ jobs: echo 'Error: Prettier diff detected! Please run `npm run prettier` and commit the changes.' exit 1 fi + + - name: Run unused style searcher + shell: bash + run: ./.github/scripts/findUnusedKeys.sh From d47feeee66e09807971ccd055333bb97e0de19ea Mon Sep 17 00:00:00 2001 From: Eduardo Date: Mon, 11 Sep 2023 16:39:04 +0200 Subject: [PATCH 024/151] fixed some lint issues --- .github/scripts/findUnusedKeys.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/scripts/findUnusedKeys.sh b/.github/scripts/findUnusedKeys.sh index 1ab00662dfe7..0dbd5c033d5f 100755 --- a/.github/scripts/findUnusedKeys.sh +++ b/.github/scripts/findUnusedKeys.sh @@ -104,7 +104,7 @@ lookfor_unused_keywords() { fi fi done < <(grep -E -o '\bstyles\.[a-zA-Z0-9_.]*' "$file" | grep -v '\/\/' | grep -vE '\/\*.*\*\/') - done < <(find $SRC_DIR -type f \( "${FILE_EXTENSIONS[@]}" \)) + done < <(find "${SRC_DIR}" -type f \( "${FILE_EXTENSIONS[@]}" \)) } @@ -160,7 +160,7 @@ find_utility_styles_store_prefix() { echo "$variable_trimmed" >> "$UTILITY_STYLES_KEYS_FILE" done < <(grep -E -o './utilities/[a-zA-Z0-9_-]+' "$file" | grep -v '\/\/' | grep -vE '\/\*.*\*\/') - done < <(find $STYLES_DIR -type f \( "${FILE_EXTENSIONS[@]}" \)) + done < <(find "${STYLES_DIR}" -type f \( "${FILE_EXTENSIONS[@]}" \)) # Sort and remove duplicates from the temporary file sort -u -o "${UTILITY_STYLES_KEYS_FILE}" "${UTILITY_STYLES_KEYS_FILE}" @@ -180,7 +180,7 @@ find_utility_usage_as_styles() { fi find_styles_and_store_keys "${file}" "${root_key}" - done < <(find $UTILITIES_STYLES_FILE -type f \( "${FILE_EXTENSIONS[@]}" \)) + done < <(find "${UTILITIES_STYLES_FILE}" -type f \( "${FILE_EXTENSIONS[@]}" \)) } lookfor_unused_utilities() { @@ -199,7 +199,7 @@ lookfor_unused_utilities() { remove_keyword "${variable}" remove_keyword "${match}" done < <(grep -E -o "$original_keyword\.[a-zA-Z0-9_-]+" "$file" | grep -v '\/\/' | grep -vE '\/\*.*\*\/') - done < <(find $STYLES_DIR -type f \( "${FILE_EXTENSIONS[@]}" \)) + done < <(find "${STYLES_DIR}" -type f \( "${FILE_EXTENSIONS[@]}" \)) done < "$UTILITY_STYLES_KEYS_FILE" } From 3af3896cf768f7919a4bdf59bca46875a9396d53 Mon Sep 17 00:00:00 2001 From: Jasper Huang Date: Mon, 11 Sep 2023 11:25:17 -0700 Subject: [PATCH 025/151] update type definitions --- src/types/onyx/Policy.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index 87382090f294..4df45e9d1146 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -4,22 +4,22 @@ import * as OnyxCommon from './OnyxCommon'; type Policy = { /** The ID of the policy */ - id?: string; + id: string; /** The name of the policy */ - name?: string; + name: string; /** The current user's role in the policy */ - role?: ValueOf; + role: ValueOf; /** The policy type */ - type?: ValueOf; + type: ValueOf; /** The email of the policy owner */ - owner?: string; + owner: string; /** The output currency for the policy */ - outputCurrency?: string; + outputCurrency: string; /** The URL for the policy avatar */ avatar?: string; @@ -33,9 +33,9 @@ type Policy = { lastModified?: string; customUnits?: Record; - areChatRoomsEnabled?: boolean; + areChatRoomsEnabled: boolean; - isPolicyExpenseChatEnabled?: boolean; + isPolicyExpenseChatEnabled: boolean; }; export default Policy; From 4ab225844f4807cd41e819974057ac874d396a73 Mon Sep 17 00:00:00 2001 From: Jasper Huang Date: Mon, 11 Sep 2023 11:26:48 -0700 Subject: [PATCH 026/151] update comment --- src/libs/PolicyUtils.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/PolicyUtils.js b/src/libs/PolicyUtils.js index 743dfba34382..5847452537ef 100644 --- a/src/libs/PolicyUtils.js +++ b/src/libs/PolicyUtils.js @@ -6,6 +6,7 @@ import ONYXKEYS from '../ONYXKEYS'; /** * Filter out the active policies, which will exclude policies with pending deletion + * These are policies that we can use to create reports with in NewDot. * @param {Object} policies * @returns {Array} */ From 9f3e7997b97b47a60b767d369c90ebe8342df63c Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Tue, 12 Sep 2023 18:42:43 +0200 Subject: [PATCH 027/151] Init device capabilities migration --- .../{index.native.js => index.native.ts} | 0 .../canUseTouchScreen/{index.js => index.ts} | 10 +++++----- src/libs/DeviceCapabilities/canUseTouchScreen/types.ts | 3 +++ .../{index.native.js => index.native.ts} | 0 .../hasHoverSupport/{index.js => index.ts} | 0 src/libs/DeviceCapabilities/hasHoverSupport/types.ts | 0 src/libs/DeviceCapabilities/{index.js => index.ts} | 0 7 files changed, 8 insertions(+), 5 deletions(-) rename src/libs/DeviceCapabilities/canUseTouchScreen/{index.native.js => index.native.ts} (100%) rename src/libs/DeviceCapabilities/canUseTouchScreen/{index.js => index.ts} (85%) create mode 100644 src/libs/DeviceCapabilities/canUseTouchScreen/types.ts rename src/libs/DeviceCapabilities/hasHoverSupport/{index.native.js => index.native.ts} (100%) rename src/libs/DeviceCapabilities/hasHoverSupport/{index.js => index.ts} (100%) create mode 100644 src/libs/DeviceCapabilities/hasHoverSupport/types.ts rename src/libs/DeviceCapabilities/{index.js => index.ts} (100%) diff --git a/src/libs/DeviceCapabilities/canUseTouchScreen/index.native.js b/src/libs/DeviceCapabilities/canUseTouchScreen/index.native.ts similarity index 100% rename from src/libs/DeviceCapabilities/canUseTouchScreen/index.native.js rename to src/libs/DeviceCapabilities/canUseTouchScreen/index.native.ts diff --git a/src/libs/DeviceCapabilities/canUseTouchScreen/index.js b/src/libs/DeviceCapabilities/canUseTouchScreen/index.ts similarity index 85% rename from src/libs/DeviceCapabilities/canUseTouchScreen/index.js rename to src/libs/DeviceCapabilities/canUseTouchScreen/index.ts index 17dcc9dffd73..e35dadf312be 100644 --- a/src/libs/DeviceCapabilities/canUseTouchScreen/index.js +++ b/src/libs/DeviceCapabilities/canUseTouchScreen/index.ts @@ -1,16 +1,16 @@ +import CanUseTouchScreen from './types'; + /** * Allows us to identify whether the platform has a touchscreen. * * https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent - * - * @returns {Boolean} */ -function canUseTouchScreen() { +const canUseTouchScreen: CanUseTouchScreen = () => { let hasTouchScreen = false; if ('maxTouchPoints' in navigator) { hasTouchScreen = navigator.maxTouchPoints > 0; } else if ('msMaxTouchPoints' in navigator) { - hasTouchScreen = navigator.msMaxTouchPoints > 0; + hasTouchScreen = (navigator as Navigator)?.msMaxTouchPoints > 0; } else { const mQ = window.matchMedia && matchMedia('(pointer:coarse)'); if (mQ && mQ.media === '(pointer:coarse)') { @@ -24,6 +24,6 @@ function canUseTouchScreen() { } } return hasTouchScreen; -} +}; export default canUseTouchScreen; diff --git a/src/libs/DeviceCapabilities/canUseTouchScreen/types.ts b/src/libs/DeviceCapabilities/canUseTouchScreen/types.ts new file mode 100644 index 000000000000..6b71ecffeb05 --- /dev/null +++ b/src/libs/DeviceCapabilities/canUseTouchScreen/types.ts @@ -0,0 +1,3 @@ +type CanUseTouchScreen = () => boolean; + +export default CanUseTouchScreen; diff --git a/src/libs/DeviceCapabilities/hasHoverSupport/index.native.js b/src/libs/DeviceCapabilities/hasHoverSupport/index.native.ts similarity index 100% rename from src/libs/DeviceCapabilities/hasHoverSupport/index.native.js rename to src/libs/DeviceCapabilities/hasHoverSupport/index.native.ts diff --git a/src/libs/DeviceCapabilities/hasHoverSupport/index.js b/src/libs/DeviceCapabilities/hasHoverSupport/index.ts similarity index 100% rename from src/libs/DeviceCapabilities/hasHoverSupport/index.js rename to src/libs/DeviceCapabilities/hasHoverSupport/index.ts diff --git a/src/libs/DeviceCapabilities/hasHoverSupport/types.ts b/src/libs/DeviceCapabilities/hasHoverSupport/types.ts new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/libs/DeviceCapabilities/index.js b/src/libs/DeviceCapabilities/index.ts similarity index 100% rename from src/libs/DeviceCapabilities/index.js rename to src/libs/DeviceCapabilities/index.ts From d0a1cba2c8fa06893896d018f5b48c692482c42b Mon Sep 17 00:00:00 2001 From: Jasper Huang Date: Tue, 12 Sep 2023 11:01:43 -0700 Subject: [PATCH 028/151] add comments --- src/types/onyx/Policy.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index 4df45e9d1146..35855402807c 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -27,14 +27,25 @@ type Policy = { /** Error objects keyed by field name containing errors keyed by microtime */ errorFields?: OnyxCommon.ErrorFields; + /** Indicates the type of change made to the policy that hasn't been synced with the server yet */ pendingAction?: OnyxCommon.PendingAction; + + /** A list of errors keyed by microtime */ errors: OnyxCommon.Errors; + + /** Whether this policy was loaded from a policy summary, or loaded completely with all of its values */ isFromFullPolicy?: boolean; + + /** When this policy was last modified */ lastModified?: string; + + /** The custom units data for this policy */ customUnits?: Record; + /** Whether chat rooms can be created and used on this policy. Enabled manually by CQ/JS snippet. */ areChatRoomsEnabled: boolean; + /** Whether policy expense chats can be created and used on this policy. Enabled manually by CQ/JS snippet. */ isPolicyExpenseChatEnabled: boolean; }; From b64ece2f38679e7841b7b2ae7e6f90d7b8ebb18e Mon Sep 17 00:00:00 2001 From: Jasper Huang Date: Tue, 12 Sep 2023 11:02:39 -0700 Subject: [PATCH 029/151] update comments --- src/types/onyx/Policy.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index 35855402807c..df4a1364a894 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -42,10 +42,10 @@ type Policy = { /** The custom units data for this policy */ customUnits?: Record; - /** Whether chat rooms can be created and used on this policy. Enabled manually by CQ/JS snippet. */ + /** Whether chat rooms can be created and used on this policy. Enabled manually by CQ/JS snippet. Always true for free policies. */ areChatRoomsEnabled: boolean; - /** Whether policy expense chats can be created and used on this policy. Enabled manually by CQ/JS snippet. */ + /** Whether policy expense chats can be created and used on this policy. Enabled manually by CQ/JS snippet. Always true for free policies. */ isPolicyExpenseChatEnabled: boolean; }; From ebf2e6683d3f019a8549984c72befd534c553401 Mon Sep 17 00:00:00 2001 From: Eduardo Date: Tue, 12 Sep 2023 20:24:16 +0200 Subject: [PATCH 030/151] Improved script find objects and fields with arrow function on different moments, removed unused styles --- .github/scripts/findUnusedKeys.sh | 83 ++++++-- src/styles/styles.js | 336 +----------------------------- 2 files changed, 67 insertions(+), 352 deletions(-) diff --git a/.github/scripts/findUnusedKeys.sh b/.github/scripts/findUnusedKeys.sh index 0dbd5c033d5f..85d8112e8f13 100755 --- a/.github/scripts/findUnusedKeys.sh +++ b/.github/scripts/findUnusedKeys.sh @@ -15,12 +15,6 @@ readonly AMOUNT_LINES_TO_SHOW=3 readonly FILE_EXTENSIONS=('-name' '*.js' '-o' '-name' '*.jsx' '-o' '-name' '*.ts' '-o' '-name' '*.tsx') -# Regex -readonly OBJ_PROP_DECLARATION_REGEX="^[[:space:]]*(const|let|var)[[:space:]]+([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*\{|^[[:space:]]*([a-zA-Z0-9_-]+\.)?[a-zA-Z0-9_-]+:[[:space:]]*\{|^[[:space:]]*\}" -readonly OBJ_DEFINITION_REGEX="^[[:space:]]*(const|let|var)[[:space:]]+([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*\{" -readonly CAPTURE_ARROW_FUNC_REGEX="^[[:space:]]*([a-zA-Z0-9_-])+:[[:space:]]*\(.*\)[[:space:]]*'=>'[[:space:]]*\(\{" -readonly CAPTURE_OBJ_ARROW_FUNC_REGEX="^[[:space:]]*([a-zA-Z0-9_-]+\.)?[a-zA-Z0-9_-]+:[[:space:]]*\{|^[[:space:]]*([a-zA-Z0-9_-])+:[[:space:]]*\(.*\)[[:space:]]*'=>'[[:space:]]*\(\{" - source scripts/shellUtils.sh # trap ctrl-c and call ctrl_c() @@ -38,7 +32,11 @@ ctrl_c() { count_lines() { local file=$1 - wc -l < "$file" + if [[ -e "$file" ]]; then + wc -l < "$file" + else + echo "File not found: $file" + fi } # Read the style file with unused keys @@ -109,24 +107,39 @@ lookfor_unused_keywords() { # Function to find and store keys from a file -find_styles_and_store_keys() { +find_styles_object_and_store_keys() { local file="$1" local base_name="${2:-styles}" # Set styles as default local parent_keys=() local root_key="" local line_number=0 + local inside_arrow_function=false while IFS= read -r line; do ((line_number++)) + # Check if we are inside an arrow function and we find a closing curly brace + if [[ "$inside_arrow_function" == true ]]; then + if [[ "$line" =~ ^[[:space:]]*\}\) ]]; then + inside_arrow_function=false + fi + continue + fi + + # Check if we are inside an arrow function + if [[ "$line" =~ ^[[:space:]]*([a-zA-Z0-9_-])+:[[:space:]]*\(.*\)[[:space:]]*'=>'[[:space:]]*\(\{ ]]; then + inside_arrow_function=true + continue + fi + # Skip lines that are not key-related - if [[ ! "$line" =~ $OBJ_PROP_DECLARATION_REGEX && ! "$line" =~ $CAPTURE_ARROW_FUNC_REGEX ]]; then + if [[ ! "$line" =~ ^[[:space:]]*(const|let|var)[[:space:]]+([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*\{|^[[:space:]]*([a-zA-Z0-9_-]+\.)?[a-zA-Z0-9_-]+:[[:space:]]*\{|^[[:space:]]*\} ]]; then continue fi - if [[ "$line" =~ $OBJ_DEFINITION_REGEX ]]; then + if [[ "$line" =~ ^[[:space:]]*(const|let|var)[[:space:]]+([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*\{ ]]; then root_key="${BASH_REMATCH[2]%%:*{*)}" - elif [[ "$line" =~ $CAPTURE_OBJ_ARROW_FUNC_REGEX ]]; then + elif [[ "$line" =~ ^[[:space:]]*([a-zA-Z0-9_-]+\.)?[a-zA-Z0-9_-]+:[[:space:]]*\{|^[[:space:]]*([a-zA-Z0-9_-])+:[[:space:]]*\(.*\)[[:space:]]*'=>'[[:space:]]*\(\{ ]]; then # Removing all the extra lines after the ":" local key="${line%%:*}" key="${key// /}" # Trim spaces @@ -150,6 +163,42 @@ find_styles_and_store_keys() { done < "$file" } +find_styles_functions_and_store_keys() { + local file="$1" + local line_number=0 + local inside_object=false + local inside_arrow_function=false + local key="" + + while IFS= read -r line; do + ((line_number++)) + + # Check if we are inside an arrow function + if [[ "$line" =~ ^[[:space:]]*([a-zA-Z0-9_-])+:[[:space:]]*\(.*\)[[:space:]]*'=>'[[:space:]]*\(\{ ]]; then + inside_arrow_function=true + key="${line%%:*}" + key="${key// /}" # Trim spaces + echo "styles.${key}:${file}:${line_number}" >> "$STYLES_KEYS_FILE" + fi + + # If we are inside an arrow function and we find an opening curly brace, + # then set inside_object to true, indicating we are inside an object. + if [[ "$inside_arrow_function" == true && "$line" =~ ^[[:space:]]*\{ ]]; then + inside_object=true + fi + + # If we are inside an object, continue to the next line. + if [[ "$inside_object" == true ]]; then + continue + fi + + # If we find a closing curly brace, reset the inside_object flag. + if [[ "$line" =~ ^[[:space:]]*\},?$ ]]; then + inside_object=false + fi + done < "$file" +} + find_utility_styles_store_prefix() { # Loop through all files in the src folder while read -r file; do @@ -179,7 +228,7 @@ find_utility_usage_as_styles() { continue fi - find_styles_and_store_keys "${file}" "${root_key}" + find_styles_object_and_store_keys "${file}" "${root_key}" done < <(find "${UTILITIES_STYLES_FILE}" -type f \( "${FILE_EXTENSIONS[@]}" \)) } @@ -203,19 +252,19 @@ lookfor_unused_utilities() { done < "$UTILITY_STYLES_KEYS_FILE" } +echo "🔍 Looking for styles." # Find and store the name of the utility files as keys find_utility_styles_store_prefix +find_utility_usage_as_styles # Find and store keys from styles.js -find_styles_and_store_keys "$STYLES_FILE" +find_styles_object_and_store_keys "$STYLES_FILE" +find_styles_functions_and_store_keys "$STYLES_FILE" -find_utility_usage_as_styles +echo "🗄️ Now going through the codebase and looking for unused keys." # Look for usages of utilities into src/styles lookfor_unused_utilities - -echo "⏱️ Now going through the list and looking for unused keys." - lookfor_unused_keywords final_styles_line_count=$(count_lines "$STYLES_KEYS_FILE") diff --git a/src/styles/styles.js b/src/styles/styles.js index 1c1340600a51..2fd346bed64c 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -174,12 +174,6 @@ const styles = { ...themeColors, ...textUnderline, - rateCol: { - margin: 0, - padding: 0, - flexBasis: '48%', - }, - autoCompleteSuggestionsContainer: { backgroundColor: themeColors.appBG, borderRadius: 8, @@ -236,13 +230,6 @@ const styles = { borderRadius: 12, }, - unitCol: { - margin: 0, - padding: 0, - marginLeft: '4%', - flexBasis: '48%', - }, - webViewStyles, link, @@ -265,19 +252,6 @@ const styles = { backgroundColor: themeColors.appBG, }, - h1: { - color: themeColors.heading, - fontFamily: fontFamily.EXP_NEUE_BOLD, - fontSize: variables.fontSizeh1, - fontWeight: fontWeightBold, - }, - - h3: { - fontFamily: fontFamily.EXP_NEUE_BOLD, - fontSize: variables.fontSizeNormal, - fontWeight: fontWeightBold, - }, - h4: { fontFamily: fontFamily.EXP_NEUE_BOLD, fontSize: variables.fontSizeLabel, @@ -428,10 +402,6 @@ const styles = { color: themeColors.textSupporting, }, - colorHeading: { - color: themeColors.heading, - }, - bgTransparent: { backgroundColor: 'transparent', }, @@ -632,10 +602,6 @@ const styles = { backgroundColor: themeColors.activeComponentBG, }, - fontWeightBold: { - fontWeight: fontWeightBold, - }, - touchableButtonImage: { alignItems: 'center', height: variables.componentSizeNormal, @@ -829,10 +795,6 @@ const styles = { height: CONST.DESKTOP_HEADER_PADDING, }, - pushTextRight: { - left: 100000, - }, - reportOptions: { marginLeft: 8, }, @@ -1104,10 +1066,6 @@ const styles = { noOutline: addOutlineWidth({}, 0), - errorOutline: { - borderColor: themeColors.danger, - }, - textLabelSupporting: { fontFamily: fontFamily.EXP_NEUE, fontSize: variables.fontSizeLabel, @@ -1173,13 +1131,6 @@ const styles = { marginBottom: 4, }, - desktopRedirectPage: { - backgroundColor: themeColors.appBG, - minHeight: '100%', - flex: 1, - alignItems: 'center', - }, - signInPage: { backgroundColor: themeColors.highlightBG, minHeight: '100%', @@ -1559,11 +1510,6 @@ const styles = { width: 18, }, - chatContent: { - flex: 4, - justifyContent: 'flex-end', - }, - chatContentScrollView: { flexGrow: 1, justifyContent: 'flex-start', @@ -1718,11 +1664,6 @@ const styles = { textAlignVertical: 'top', }, - editInputComposeSpacing: { - backgroundColor: themeColors.transparent, - marginVertical: 8, - }, - // composer padding should not be modified unless thoroughly tested against the cases in this PR: #12669 textInputComposeSpacing: { paddingVertical: 5, @@ -1844,23 +1785,6 @@ const styles = { width: 200, }, - chatSwticherPillWrapper: { - marginTop: 5, - marginRight: 4, - }, - - navigationModalOverlay: { - ...userSelect.userSelectNone, - position: 'absolute', - width: '100%', - height: '100%', - transform: [ - { - translateX: -variables.sideBarWidth, - }, - ], - }, - sidebarVisible: { borderRightWidth: 1, }, @@ -1885,14 +1809,6 @@ const styles = { borderRadius: 24, }, - singleSubscript: { - height: variables.iconSizeNormal, - width: variables.iconSizeNormal, - backgroundColor: themeColors.icon, - borderRadius: 20, - zIndex: 1, - }, - singleAvatarSmall: { height: 18, width: 18, @@ -1936,17 +1852,6 @@ const styles = { right: 0, }, - leftSideLargeAvatar: { - left: 15, - }, - - rightSideLargeAvatar: { - right: 15, - zIndex: 2, - borderWidth: 4, - borderRadius: 100, - }, - secondAvatarInline: { bottom: -3, right: -25, @@ -1961,18 +1866,6 @@ const styles = { height: variables.avatarSizeLarge, }, - avatarNormal: { - height: variables.componentSizeNormal, - width: variables.componentSizeNormal, - borderRadius: variables.componentSizeNormal, - }, - - avatarSmall: { - height: variables.avatarSizeSmall, - width: variables.avatarSizeSmall, - borderRadius: variables.avatarSizeSmall, - }, - avatarInnerText: { color: themeColors.textLight, fontSize: variables.fontSizeSmall, @@ -1989,21 +1882,6 @@ const styles = { textAlign: 'center', }, - avatarSpace: { - top: 3, - left: 3, - }, - - avatar: { - backgroundColor: themeColors.sidebar, - borderColor: themeColors.sidebar, - }, - - focusedAvatar: { - backgroundColor: themeColors.border, - borderColor: themeColors.border, - }, - emptyAvatar: { height: variables.avatarSizeNormal, width: variables.avatarSizeNormal, @@ -2050,11 +1928,6 @@ const styles = { marginRight: variables.avatarChatSpacing - 4, }, - modalViewContainer: { - alignItems: 'center', - flex: 1, - }, - borderTop: { borderTopWidth: variables.borderTopWidth, borderColor: themeColors.border, @@ -2144,14 +2017,6 @@ const styles = { ...(isSmallScreenWidth && flex.flex1), }), - modalCenterContentContainer: { - flex: 1, - flexDirection: 'column', - justifyContent: 'center', - alignItems: 'center', - backgroundColor: themeColors.modalBackdrop, - }, - centeredModalStyles: (isSmallScreenWidth, isFullScreenWhenSmall) => ({ borderWidth: isSmallScreenWidth && !isFullScreenWhenSmall ? 1 : 0, marginHorizontal: isSmallScreenWidth ? 0 : 20, @@ -2174,28 +2039,6 @@ const styles = { alignItems: 'center', }, - notFoundSafeArea: { - flex: 1, - backgroundColor: themeColors.heading, - }, - - notFoundView: { - flex: 1, - alignItems: 'center', - paddingTop: 40, - paddingBottom: 40, - justifyContent: 'space-between', - }, - - notFoundLogo: { - width: 202, - height: 63, - }, - - notFoundContent: { - alignItems: 'center', - }, - notFoundTextHeader: { ...headlineFont, color: themeColors.heading, @@ -2206,20 +2049,6 @@ const styles = { textAlign: 'center', }, - notFoundTextBody: { - color: themeColors.componentBG, - fontFamily: fontFamily.EXP_NEUE_BOLD, - fontWeight: fontWeightBold, - fontSize: 15, - }, - - notFoundButtonText: { - color: themeColors.link, - fontFamily: fontFamily.EXP_NEUE_BOLD, - fontWeight: fontWeightBold, - fontSize: 15, - }, - blockingViewContainer: { paddingBottom: variables.contentHeaderHeight, }, @@ -2270,18 +2099,6 @@ const styles = { justifyContent: 'space-around', }, - settingsPageColumn: { - width: '100%', - alignItems: 'center', - justifyContent: 'space-around', - }, - - settingsPageContainer: { - justifyContent: 'space-between', - alignItems: 'center', - width: '100%', - }, - twoFactorAuthSection: { backgroundColor: themeColors.appBG, padding: 0, @@ -2419,18 +2236,6 @@ const styles = { left: -16, }, - svgAvatarBorder: { - borderRadius: 100, - overflow: 'hidden', - }, - - displayName: { - fontSize: variables.fontSizeLarge, - fontFamily: fontFamily.EXP_NEUE_BOLD, - fontWeight: fontWeightBold, - color: themeColors.heading, - }, - pageWrapper: { width: '100%', alignItems: 'center', @@ -2502,21 +2307,11 @@ const styles = { transform: [{rotate: '180deg'}], }, - navigationSceneContainer: { - backgroundColor: themeColors.appBG, - }, - navigationScreenCardStyle: { backgroundColor: themeColors.appBG, height: '100%', }, - navigationSceneFullScreenWrapper: { - borderRadius: variables.componentBorderRadiusCard, - overflow: 'hidden', - height: '100%', - }, - invisible: { position: 'absolute', opacity: 0, @@ -2554,14 +2349,6 @@ const styles = { paddingBottom: 0, }, - detailsPageSectionVersion: { - alignSelf: 'center', - color: themeColors.textSupporting, - fontSize: variables.fontSizeSmall, - height: 24, - lineHeight: 20, - }, - switchTrack: { width: 50, height: 28, @@ -2711,12 +2498,6 @@ const styles = { alignSelf: 'center', }, - iouDetailsContainer: { - flexGrow: 1, - paddingStart: 20, - paddingEnd: 20, - }, - codeWordWrapper: { ...codeStyles.codeWordWrapper, }, @@ -2756,11 +2537,6 @@ const styles = { zIndex: 10, }, - navigatorFullScreenLoading: { - backgroundColor: themeColors.highlightBG, - opacity: 1, - }, - reimbursementAccountFullScreenLoading: { backgroundColor: themeColors.componentBG, opacity: 0.8, @@ -2845,40 +2621,6 @@ const styles = { height: '100%', }, - fullscreenCard: { - position: 'absolute', - left: 0, - top: 0, - width: '100%', - height: '100%', - }, - - fullscreenCardWeb: { - left: 'auto', - right: '-24%', - top: '-18%', - height: '120%', - }, - - fullscreenCardWebCentered: { - left: '0', - right: '0', - top: '0', - height: '60%', - }, - - fullscreenCardMobile: { - left: '-20%', - top: '-30%', - width: '150%', - }, - - fullscreenCardMediumScreen: { - left: '-15%', - top: '-30%', - width: '145%', - }, - smallEditIcon: { alignItems: 'center', backgroundColor: themeColors.buttonHoveredBG, @@ -2897,41 +2639,6 @@ const styles = { bottom: -4, }, - workspaceCard: { - width: '100%', - height: 400, - borderRadius: variables.componentBorderRadiusCard, - overflow: 'hidden', - backgroundColor: themeColors.heroCard, - }, - - workspaceCardMobile: { - height: 475, - }, - - workspaceCardMediumScreen: { - height: 540, - }, - - workspaceCardMainText: { - fontSize: variables.fontSizeXXXLarge, - fontWeight: 'bold', - lineHeight: variables.fontSizeXXXLarge, - }, - - workspaceCardContent: { - zIndex: 1, - padding: 50, - }, - - workspaceCardContentMediumScreen: { - padding: 25, - }, - - workspaceCardCTA: { - width: 250, - }, - autoGrowHeightMultilineInput: { maxHeight: 115, }, @@ -3011,12 +2718,6 @@ const styles = { opacity: variables.overlayOpacity, }, - communicationsLinkIcon: { - right: -36, - top: 0, - bottom: 0, - }, - shortTermsBorder: { borderWidth: 1, borderColor: themeColors.border, @@ -3202,10 +2903,6 @@ const styles = { fontSize: 48, }, - closeAccountMessageInput: { - height: 153, - }, - imageCropContainer: { overflow: 'hidden', alignItems: 'center', @@ -3302,24 +2999,11 @@ const styles = { alignItems: 'center', }, - callRequestSection: { - backgroundColor: themeColors.appBG, - paddingHorizontal: 0, - paddingBottom: 0, - marginHorizontal: 0, - marginBottom: 0, - }, - archivedReportFooter: { borderRadius: variables.componentBorderRadius, ...wordBreak.breakWord, }, - saveButtonPadding: { - paddingLeft: 18, - paddingRight: 18, - }, - deeplinkWrapperContainer: { padding: 20, flex: 1, @@ -3365,11 +3049,7 @@ const styles = { alignSelf: 'flex-start', marginRight: 4, }, - reactionListItem: { - flexDirection: 'row', - paddingVertical: 12, - paddingHorizontal: 20, - }, + reactionListHeaderText: { color: themeColors.textSupporting, marginLeft: 8, @@ -3461,11 +3141,6 @@ const styles = { width: '100%', }, - listPickerSeparator: { - height: 1, - backgroundColor: themeColors.buttonDefaultBG, - }, - datePickerRoot: { position: 'relative', zIndex: 99, @@ -3518,15 +3193,6 @@ const styles = { width: 16, }, - validateCodeMessage: { - width: variables.modalContentMaxWidth, - textAlign: 'center', - }, - - whisper: { - backgroundColor: themeColors.cardBG, - }, - contextMenuItemPopoverMaxWidth: { maxWidth: 375, }, From 9f47e6a2fcf95df8a4cb0fef16819483090f3f02 Mon Sep 17 00:00:00 2001 From: Wildan M Date: Wed, 13 Sep 2023 11:14:39 +0700 Subject: [PATCH 031/151] Update src/libs/actions/Report.js Co-authored-by: Carlos Martins --- src/libs/actions/Report.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 2de649163079..cd3ca192d9a4 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -192,7 +192,7 @@ function subscribeToReportTypingEvents(reportID) { } /** - * Initialize our pusher subscriptions to listen for someone typing in a report. + * Initialize our pusher subscriptions to listen for someone leaving a room. * * @param {String} reportID */ From 6a1f0806474e48c60c4e05edc85ab3f8ef8a4823 Mon Sep 17 00:00:00 2001 From: Wildan M Date: Wed, 13 Sep 2023 11:21:02 +0700 Subject: [PATCH 032/151] Update src/libs/actions/Report.js Co-authored-by: Carlos Martins --- src/libs/actions/Report.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index cd3ca192d9a4..65ec101aeb6d 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -937,7 +937,7 @@ function broadcastUserIsTyping(reportID) { Pusher.sendEvent(privateReportChannelName, Pusher.TYPE.USER_IS_TYPING, typingStatus); } /** - * Broadcasts whether or not a user is leaving on a report over the report's private pusher channel. + * Broadcasts to the report's private pusher channel whether a user is leaving a report * * @param {String} reportID */ From 39c079b8d43ab38e0b3ae829f127957d15ebf7b0 Mon Sep 17 00:00:00 2001 From: Wildan M Date: Wed, 13 Sep 2023 11:23:20 +0700 Subject: [PATCH 033/151] Update src/pages/home/ReportScreen.js Co-authored-by: Carlos Martins --- src/pages/home/ReportScreen.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 74182d8db49a..0f824db5860c 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -88,7 +88,7 @@ const propTypes = { /** All of the personal details for everyone */ personalDetails: PropTypes.objectOf(personalDetailsPropType), - /** Whether user leaving current report that listen to another device leaveRoom trigger */ + /** Whether user is leaving the current report */ userLeavingStatus: PropTypes.bool, ...windowDimensionsPropTypes, From 70c6b304f3dd0edef9c9f606b876467a962298f2 Mon Sep 17 00:00:00 2001 From: Wildan M Date: Wed, 13 Sep 2023 11:24:09 +0700 Subject: [PATCH 034/151] Update src/pages/home/ReportScreen.js Co-authored-by: Carlos Martins --- src/pages/home/ReportScreen.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 0f824db5860c..ba4322d08393 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -290,7 +290,7 @@ function ReportScreen({ const prevOnyxReportID = prevReport.reportID; const routeReportID = getReportID(route); - // navigate to concierge when the room removed from another device (e.g. user leaving a room) + // Navigate to the Concierge chat if the room was removed from another device (e.g. user leaving a room) if ( // non-optimistic case userLeavingStatus || From f91b792e26b924ea6f37ab2e39d2131d2743ed7a Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Wed, 13 Sep 2023 11:28:49 +0700 Subject: [PATCH 035/151] make getNormalizedTypingStatus become generic getNormalizedStatus --- src/libs/actions/Report.js | 35 +++++++---------------------------- 1 file changed, 7 insertions(+), 28 deletions(-) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index cd3ca192d9a4..d61670aacc13 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -113,35 +113,14 @@ function getReportChannelName(reportID) { * @param {Object} typingStatus * @returns {Object} */ -function getNormalizedTypingStatus(typingStatus) { - let normalizedTypingStatus = typingStatus; +function getNormalizedStatus(status) { + let normalizedStatus = status; - if (_.first(_.keys(typingStatus)) === 'userLogin') { - normalizedTypingStatus = {[typingStatus.userLogin]: true}; + if (_.first(_.keys(status)) === 'userLogin') { + normalizedStatus = {[status.userLogin]: true}; } - return normalizedTypingStatus; -} - -/** - * There are 2 possibilities that we can receive via pusher for a user's leaving status: - * 1. The "new" way from New Expensify is passed as {[login]: Boolean} (e.g. {yuwen@expensify.com: true}), where the value - * is whether the user with that login is leaving on the report or not. - * 2. The "old" way from e.com which is passed as {userLogin: login} (e.g. {userLogin: bstites@expensify.com}) - * - * This method makes sure that no matter which we get, we return the "new" format - * - * @param {Object} leavingStatus - * @returns {Object} - */ -function getNormalizedLeavingStatus(leavingStatus) { - let normalizedLeavingStatus = leavingStatus; - - if (_.first(_.keys(leavingStatus)) === 'userLogin') { - normalizedLeavingStatus = {[leavingStatus.userLogin]: true}; - } - - return normalizedLeavingStatus; + return normalizedStatus; } /** @@ -162,7 +141,7 @@ function subscribeToReportTypingEvents(reportID) { // If the pusher message comes from OldDot, we expect the typing status to be keyed by user // login OR by 'Concierge'. If the pusher message comes from NewDot, it is keyed by accountID // since personal details are keyed by accountID. - const normalizedTypingStatus = getNormalizedTypingStatus(typingStatus); + const normalizedTypingStatus = getNormalizedStatus(typingStatus); const accountIDOrLogin = _.first(_.keys(normalizedTypingStatus)); if (!accountIDOrLogin) { @@ -209,7 +188,7 @@ function subscribeToReportLeavingEvents(reportID) { // If the pusher message comes from OldDot, we expect the leaving status to be keyed by user // login OR by 'Concierge'. If the pusher message comes from NewDot, it is keyed by accountID // since personal details are keyed by accountID. - const normalizedLeavingStatus = getNormalizedLeavingStatus(leavingStatus); + const normalizedLeavingStatus = getNormalizedStatus(leavingStatus); const accountIDOrLogin = _.first(_.keys(normalizedLeavingStatus)); if (!accountIDOrLogin) { From a658b186749b240e04a340fb59949e5cabdcfaaa Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Wed, 13 Sep 2023 11:39:12 +0700 Subject: [PATCH 036/151] fix lint --- src/libs/actions/Report.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index b74e532b1c39..f0ac66923002 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -103,14 +103,14 @@ function getReportChannelName(reportID) { } /** - * There are 2 possibilities that we can receive via pusher for a user's typing status: + * There are 2 possibilities that we can receive via pusher for a user's typing/leaving status: * 1. The "new" way from New Expensify is passed as {[login]: Boolean} (e.g. {yuwen@expensify.com: true}), where the value - * is whether the user with that login is typing on the report or not. + * is whether the user with that login is typing/leaving on the report or not. * 2. The "old" way from e.com which is passed as {userLogin: login} (e.g. {userLogin: bstites@expensify.com}) * * This method makes sure that no matter which we get, we return the "new" format * - * @param {Object} typingStatus + * @param {Object} status * @returns {Object} */ function getNormalizedStatus(status) { From a842d93b5d3b1a00942146455202aa4e5662ecbc Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Wed, 13 Sep 2023 11:02:31 +0200 Subject: [PATCH 037/151] [TS migration] Migrate 'DeviceCapabilities' lib to TypeScript --- .../canUseTouchScreen/index.native.ts | 6 +++--- .../DeviceCapabilities/canUseTouchScreen/index.ts | 15 ++++++++++++--- .../hasHoverSupport/index.native.ts | 7 +++---- .../DeviceCapabilities/hasHoverSupport/index.ts | 8 +++----- .../DeviceCapabilities/hasHoverSupport/types.ts | 3 +++ 5 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/libs/DeviceCapabilities/canUseTouchScreen/index.native.ts b/src/libs/DeviceCapabilities/canUseTouchScreen/index.native.ts index 4306b0cff3f6..60980801e73c 100644 --- a/src/libs/DeviceCapabilities/canUseTouchScreen/index.native.ts +++ b/src/libs/DeviceCapabilities/canUseTouchScreen/index.native.ts @@ -1,5 +1,5 @@ -function canUseTouchScreen() { - return true; -} +import CanUseTouchScreen from './types'; + +const canUseTouchScreen: CanUseTouchScreen = () => true; export default canUseTouchScreen; diff --git a/src/libs/DeviceCapabilities/canUseTouchScreen/index.ts b/src/libs/DeviceCapabilities/canUseTouchScreen/index.ts index e35dadf312be..72798159e36c 100644 --- a/src/libs/DeviceCapabilities/canUseTouchScreen/index.ts +++ b/src/libs/DeviceCapabilities/canUseTouchScreen/index.ts @@ -1,5 +1,8 @@ +import {Merge} from 'type-fest'; import CanUseTouchScreen from './types'; +type ExtendedNavigator = Merge; + /** * Allows us to identify whether the platform has a touchscreen. * @@ -7,19 +10,25 @@ import CanUseTouchScreen from './types'; */ const canUseTouchScreen: CanUseTouchScreen = () => { let hasTouchScreen = false; + + // TypeScript removed support for msMaxTouchPoints, this doesn't mean however that + // this property doesn't exist - hence the use of ExtendedNavigator to ensure + // that the functionality doesn't change + // https://github.com/microsoft/TypeScript-DOM-lib-generator/issues/1029 if ('maxTouchPoints' in navigator) { hasTouchScreen = navigator.maxTouchPoints > 0; } else if ('msMaxTouchPoints' in navigator) { - hasTouchScreen = (navigator as Navigator)?.msMaxTouchPoints > 0; + hasTouchScreen = (navigator as ExtendedNavigator).msMaxTouchPoints > 0; } else { - const mQ = window.matchMedia && matchMedia('(pointer:coarse)'); + // Same case as for Navigator - TypeScript thinks that matchMedia is obligatory property of window although it may not be + const mQ = (window as Partial).matchMedia && matchMedia('(pointer:coarse)'); if (mQ && mQ.media === '(pointer:coarse)') { hasTouchScreen = !!mQ.matches; } else if ('orientation' in window) { hasTouchScreen = true; // deprecated, but good fallback } else { // Only as a last resort, fall back to user agent sniffing - const UA = navigator.userAgent; + const UA = (navigator as ExtendedNavigator).userAgent; hasTouchScreen = /\b(BlackBerry|webOS|iPhone|IEMobile)\b/i.test(UA) || /\b(Android|Windows Phone|iPad|iPod)\b/i.test(UA); } } diff --git a/src/libs/DeviceCapabilities/hasHoverSupport/index.native.ts b/src/libs/DeviceCapabilities/hasHoverSupport/index.native.ts index d77fcc17448a..097b3b0cbba1 100644 --- a/src/libs/DeviceCapabilities/hasHoverSupport/index.native.ts +++ b/src/libs/DeviceCapabilities/hasHoverSupport/index.native.ts @@ -1,9 +1,8 @@ +import HasHoverSupport from './types'; + /** * Allows us to identify whether the platform is hoverable. - * - * @returns {Boolean} */ - -const hasHoverSupport = () => false; +const hasHoverSupport: HasHoverSupport = () => false; export default hasHoverSupport; diff --git a/src/libs/DeviceCapabilities/hasHoverSupport/index.ts b/src/libs/DeviceCapabilities/hasHoverSupport/index.ts index 84a3fbbc5ed1..df62e6681548 100644 --- a/src/libs/DeviceCapabilities/hasHoverSupport/index.ts +++ b/src/libs/DeviceCapabilities/hasHoverSupport/index.ts @@ -1,10 +1,8 @@ +import HasHoverSupport from './types'; + /** * Allows us to identify whether the platform is hoverable. - * - * @returns {Boolean} */ -function hasHoverSupport() { - return window.matchMedia('(hover: hover) and (pointer: fine)').matches; -} +const hasHoverSupport: HasHoverSupport = () => window.matchMedia('(hover: hover) and (pointer: fine)').matches; export default hasHoverSupport; diff --git a/src/libs/DeviceCapabilities/hasHoverSupport/types.ts b/src/libs/DeviceCapabilities/hasHoverSupport/types.ts index e69de29bb2d1..b8fe944cf88e 100644 --- a/src/libs/DeviceCapabilities/hasHoverSupport/types.ts +++ b/src/libs/DeviceCapabilities/hasHoverSupport/types.ts @@ -0,0 +1,3 @@ +type HasHoverSupport = () => boolean; + +export default HasHoverSupport; From cd0804b89612bfb6a802cfeed704aff9750a95b5 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Thu, 14 Sep 2023 09:54:35 +0700 Subject: [PATCH 038/151] fix: 26882 --- src/components/ImageView/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ImageView/index.js b/src/components/ImageView/index.js index c92bd7738253..5d6d1b609ab3 100644 --- a/src/components/ImageView/index.js +++ b/src/components/ImageView/index.js @@ -225,14 +225,14 @@ function ImageView({isAuthTokenRequired, url, fileName}) { source={{uri: url}} isAuthTokenRequired={isAuthTokenRequired} // Hide image until finished loading to prevent showing preview with wrong dimensions. - style={isLoading ? undefined : [styles.w100, styles.h100]} + style={(isLoading || zoomScale === 0) ? undefined : [styles.w100, styles.h100]} // When Image dimensions are lower than the container boundary(zoomscale <= 1), use `contain` to render the image with natural dimensions. // Both `center` and `contain` keeps the image centered on both x and y axis. resizeMode={zoomScale > 1 ? Image.resizeMode.center : Image.resizeMode.contain} onLoadStart={imageLoadingStart} onLoad={imageLoad} /> - {isLoading && } + {(isLoading || zoomScale === 0) && } ); } From 32d135582c6c20e3fa5ab5f0e88ff9a47c9c8532 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Thu, 14 Sep 2023 16:43:09 +0200 Subject: [PATCH 039/151] delegate category selection to parent --- .../CategoryPicker/categoryPickerPropTypes.js | 16 ++++------ src/components/CategoryPicker/index.js | 30 ++++--------------- src/pages/iou/MoneyRequestCategoryPage.js | 27 +++++++++++++++-- 3 files changed, 34 insertions(+), 39 deletions(-) diff --git a/src/components/CategoryPicker/categoryPickerPropTypes.js b/src/components/CategoryPicker/categoryPickerPropTypes.js index b8e24c199a73..6f2800a5d98f 100644 --- a/src/components/CategoryPicker/categoryPickerPropTypes.js +++ b/src/components/CategoryPicker/categoryPickerPropTypes.js @@ -2,14 +2,11 @@ import PropTypes from 'prop-types'; import categoryPropTypes from '../categoryPropTypes'; const propTypes = { - /** The report ID of the IOU */ - reportID: PropTypes.string.isRequired, - /** The policyID we are getting categories for */ policyID: PropTypes.string, - /** The type of IOU report, i.e. bill, request, send */ - iouType: PropTypes.string.isRequired, + /** The selected category of an expense */ + selectedCategory: PropTypes.string, /* Onyx Props */ /** Collection of categories attached to a policy */ @@ -19,18 +16,15 @@ const propTypes = { /** Collection of recently used categories attached to a policy */ policyRecentlyUsedCategories: PropTypes.arrayOf(PropTypes.string), - /* Onyx Props */ - /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ - iou: PropTypes.shape({ - category: PropTypes.string.isRequired, - }), + /** Callback to fire when a category is pressed */ + onSubmit: PropTypes.func.isRequired, }; const defaultProps = { policyID: '', + selectedCategory: '', policyCategories: {}, policyRecentlyUsedCategories: [], - iou: {}, }; export {propTypes, defaultProps}; diff --git a/src/components/CategoryPicker/index.js b/src/components/CategoryPicker/index.js index 91c7e82e7887..66ac0bf14d4a 100644 --- a/src/components/CategoryPicker/index.js +++ b/src/components/CategoryPicker/index.js @@ -5,15 +5,12 @@ import lodashGet from 'lodash/get'; import ONYXKEYS from '../../ONYXKEYS'; import {propTypes, defaultProps} from './categoryPickerPropTypes'; import styles from '../../styles/styles'; -import Navigation from '../../libs/Navigation/Navigation'; -import ROUTES from '../../ROUTES'; import CONST from '../../CONST'; -import * as IOU from '../../libs/actions/IOU'; import * as OptionsListUtils from '../../libs/OptionsListUtils'; import OptionsSelector from '../OptionsSelector'; import useLocalize from '../../hooks/useLocalize'; -function CategoryPicker({policyCategories, reportID, iouType, iou, policyRecentlyUsedCategories}) { +function CategoryPicker({selectedCategory, policyCategories, policyRecentlyUsedCategories, onSubmit}) { const {translate} = useLocalize(); const [searchValue, setSearchValue] = useState(''); @@ -21,18 +18,18 @@ function CategoryPicker({policyCategories, reportID, iouType, iou, policyRecentl const isCategoriesCountBelowThreshold = policyCategoriesCount < CONST.CATEGORY_LIST_THRESHOLD; const selectedOptions = useMemo(() => { - if (!iou.category) { + if (!selectedCategory) { return []; } return [ { - name: iou.category, + name: selectedCategory, enabled: true, accountID: null, }, ]; - }, [iou.category]); + }, [selectedCategory]); const initialFocusedIndex = useMemo(() => { if (isCategoriesCountBelowThreshold && selectedOptions.length > 0) { @@ -53,20 +50,6 @@ function CategoryPicker({policyCategories, reportID, iouType, iou, policyRecentl const headerMessage = OptionsListUtils.getHeaderMessage(lodashGet(sections, '[0].data.length', 0) > 0, false, searchValue); const shouldShowTextInput = !isCategoriesCountBelowThreshold; - const navigateBack = () => { - Navigation.goBack(ROUTES.getMoneyRequestConfirmationRoute(iouType, reportID)); - }; - - const updateCategory = (category) => { - if (category.searchText === iou.category) { - IOU.resetMoneyRequestCategory(); - } else { - IOU.setMoneyRequestCategory(category.searchText); - } - - navigateBack(); - }; - return ( ); } @@ -97,7 +80,4 @@ export default withOnyx({ policyRecentlyUsedCategories: { key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES}${policyID}`, }, - iou: { - key: ONYXKEYS.IOU, - }, })(CategoryPicker); diff --git a/src/pages/iou/MoneyRequestCategoryPage.js b/src/pages/iou/MoneyRequestCategoryPage.js index 80b88a762609..adba5755496d 100644 --- a/src/pages/iou/MoneyRequestCategoryPage.js +++ b/src/pages/iou/MoneyRequestCategoryPage.js @@ -10,6 +10,7 @@ import HeaderWithBackButton from '../../components/HeaderWithBackButton'; import CategoryPicker from '../../components/CategoryPicker'; import ONYXKEYS from '../../ONYXKEYS'; import reportPropTypes from '../reportPropTypes'; +import * as IOU from '../../libs/actions/IOU'; const propTypes = { /** Navigation route context info provided by react navigation */ @@ -26,13 +27,20 @@ const propTypes = { /** The report currently being used */ report: reportPropTypes, + + /* Onyx Props */ + /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ + iou: PropTypes.shape({ + category: PropTypes.string.isRequired, + }), }; const defaultProps = { report: {}, + iou: {}, }; -function MoneyRequestCategoryPage({route, report}) { +function MoneyRequestCategoryPage({route, report, iou}) { const {translate} = useLocalize(); const reportID = lodashGet(route, 'params.reportID', ''); @@ -42,6 +50,16 @@ function MoneyRequestCategoryPage({route, report}) { Navigation.goBack(ROUTES.getMoneyRequestConfirmationRoute(iouType, reportID)); }; + const updateCategory = (category) => { + if (category.searchText === iou.category) { + IOU.resetMoneyRequestCategory(); + } else { + IOU.setMoneyRequestCategory(category.searchText); + } + + Navigation.goBack(ROUTES.getMoneyRequestConfirmationRoute(iouType, reportID)); + }; + return ( ); @@ -69,4 +87,7 @@ export default withOnyx({ report: { key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${lodashGet(route, 'params.reportID', '')}`, }, + iou: { + key: ONYXKEYS.IOU, + }, })(MoneyRequestCategoryPage); From d7996869bf22815900c87a80f9ac9e68a1f27626 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Thu, 14 Sep 2023 16:44:05 +0200 Subject: [PATCH 040/151] implement edit request category page --- src/CONST.ts | 1 + src/pages/EditRequestCategoryPage.js | 49 ++++++++++++++++++++++++++++ src/pages/EditRequestPage.js | 26 ++++++++++++++- 3 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 src/pages/EditRequestCategoryPage.js diff --git a/src/CONST.ts b/src/CONST.ts index 1ef2f3e83246..7b6d1d1d36fb 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1349,6 +1349,7 @@ const CONST = { DATE: 'date', DESCRIPTION: 'description', MERCHANT: 'merchant', + CATEGORY: 'category', }, FOOTER: { EXPENSE_MANAGEMENT_URL: `${USE_EXPENSIFY_URL}/expense-management`, diff --git a/src/pages/EditRequestCategoryPage.js b/src/pages/EditRequestCategoryPage.js new file mode 100644 index 000000000000..ba94c2b40800 --- /dev/null +++ b/src/pages/EditRequestCategoryPage.js @@ -0,0 +1,49 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ScreenWrapper from '../components/ScreenWrapper'; +import HeaderWithBackButton from '../components/HeaderWithBackButton'; +import Navigation from '../libs/Navigation/Navigation'; +import useLocalize from '../hooks/useLocalize'; +import CategoryPicker from '../components/CategoryPicker'; + +const propTypes = { + /** Transaction default category value */ + defaultCategory: PropTypes.string.isRequired, + + /** The policyID we are getting categories for */ + policyID: PropTypes.string.isRequired, + + /** Callback to fire when the Save button is pressed */ + onSubmit: PropTypes.func.isRequired, +}; + +function EditRequestCategoryPage({defaultCategory, policyID, onSubmit}) { + const {translate} = useLocalize(); + + return ( + + + + + onSubmit({ + category: category.searchText, + }) + } + /> + + ); +} + +EditRequestCategoryPage.propTypes = propTypes; +EditRequestCategoryPage.displayName = 'EditRequestCategoryPage'; + +export default EditRequestCategoryPage; diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index 266515e29c2c..d4fc92c894d0 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -19,6 +19,7 @@ import reportPropTypes from './reportPropTypes'; import * as IOU from '../libs/actions/IOU'; import * as CurrencyUtils from '../libs/CurrencyUtils'; import FullPageNotFoundView from '../components/BlockingViews/FullPageNotFoundView'; +import EditRequestCategoryPage from './EditRequestCategoryPage'; const propTypes = { /** Route from navigation */ @@ -69,7 +70,13 @@ const defaultProps = { function EditRequestPage({report, route, parentReport, policy, session}) { const parentReportAction = ReportActionsUtils.getParentReportAction(report); const transaction = TransactionUtils.getLinkedTransaction(parentReportAction); - const {amount: transactionAmount, currency: transactionCurrency, comment: transactionDescription, merchant: transactionMerchant} = ReportUtils.getTransactionDetails(transaction); + const { + amount: transactionAmount, + currency: transactionCurrency, + comment: transactionDescription, + merchant: transactionMerchant, + category: transactionCategory, + } = ReportUtils.getTransactionDetails(transaction); const defaultCurrency = lodashGet(route, 'params.currency', '') || transactionCurrency; @@ -169,6 +176,23 @@ function EditRequestPage({report, route, parentReport, policy, session}) { ); } + if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.CATEGORY) { + return ( + { + let updatedCategory = transactionChanges.category; + // In case the same category has been selected, do reset of the category. + if (transactionCategory === updatedCategory) { + updatedCategory = ''; + } + editMoneyRequest({category: updatedCategory}); + }} + /> + ); + } + return ; } From da95354c9ad2a170f224a080e4651ef0a0ec2207 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Thu, 14 Sep 2023 16:46:25 +0200 Subject: [PATCH 041/151] preview and navigate to edit a category --- .../ReportActionItem/MoneyRequestView.js | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index a9264812b99d..7ad7dc0ce356 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -1,6 +1,7 @@ import React from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; +import _ from 'underscore'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import reportPropTypes from '../../pages/reportPropTypes'; @@ -27,6 +28,7 @@ import Image from '../Image'; import ReportActionItemImage from './ReportActionItemImage'; import * as TransactionUtils from '../../libs/TransactionUtils'; import OfflineWithFeedback from '../OfflineWithFeedback'; +import categoryPropTypes from '../categoryPropTypes'; const propTypes = { /** The report currently being looked at */ @@ -35,6 +37,10 @@ const propTypes = { /** The expense report or iou report (only will have a value if this is a transaction thread) */ parentReport: iouReportPropTypes, + /* Onyx Props */ + /** Collection of categories attached to a policy */ + policyCategories: PropTypes.objectOf(categoryPropTypes), + /** The transaction associated with the transactionThread */ transaction: transactionPropTypes, @@ -46,6 +52,7 @@ const propTypes = { const defaultProps = { parentReport: {}, + policyCategories: {}, transaction: { amount: 0, currency: CONST.CURRENCY.USD, @@ -53,7 +60,7 @@ const defaultProps = { }, }; -function MoneyRequestView({report, parentReport, shouldShowHorizontalRule, transaction}) { +function MoneyRequestView({report, parentReport, policyCategories, shouldShowHorizontalRule, transaction}) { const {isSmallScreenWidth} = useWindowDimensions(); const {translate} = useLocalize(); @@ -65,6 +72,7 @@ function MoneyRequestView({report, parentReport, shouldShowHorizontalRule, trans currency: transactionCurrency, comment: transactionDescription, merchant: transactionMerchant, + category: transactionCategory, } = ReportUtils.getTransactionDetails(transaction); const isEmptyMerchant = transactionMerchant === '' || transactionMerchant === CONST.TRANSACTION.UNKNOWN_MERCHANT || transactionMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT; @@ -72,6 +80,7 @@ function MoneyRequestView({report, parentReport, shouldShowHorizontalRule, trans const isSettled = ReportUtils.isSettled(moneyRequestReport.reportID); const canEdit = ReportUtils.canEditMoneyRequest(parentReportAction); + const shouldShowCategory = !_.isEmpty(policyCategories) || !_.isEmpty(transactionCategory); let description = `${translate('iou.amount')} • ${translate('iou.cash')}`; if (isSettled) { @@ -165,6 +174,18 @@ function MoneyRequestView({report, parentReport, shouldShowHorizontalRule, trans subtitleTextStyle={styles.textLabelError} /> + {shouldShowCategory && ( + + Navigation.navigate(ROUTES.getEditRequestRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.CATEGORY))} + /> + + )} {shouldShowHorizontalRule && } ); @@ -183,6 +204,9 @@ export default compose( policy: { key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`, }, + policyCategories: { + key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${report.policyID}`, + }, session: { key: ONYXKEYS.SESSION, }, From bfc59ba9dcf7275eb7cad57dac477d0576577190 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Thu, 14 Sep 2023 17:35:52 +0200 Subject: [PATCH 042/151] prepare api --- src/libs/TransactionUtils.js | 14 +++++++++++++- src/libs/actions/IOU.js | 13 +++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/libs/TransactionUtils.js b/src/libs/TransactionUtils.js index 4ca8b48d284e..73ba6a8116ef 100644 --- a/src/libs/TransactionUtils.js +++ b/src/libs/TransactionUtils.js @@ -141,6 +141,11 @@ function getUpdatedTransaction(transaction, transactionChanges, isFromExpenseRep shouldStopSmartscan = true; } + if (_.has(transactionChanges, 'category')) { + updatedTransaction.modifiedCategory = transactionChanges.category; + shouldStopSmartscan = true; + } + if (shouldStopSmartscan && _.has(transaction, 'receipt') && !_.isEmpty(transaction.receipt) && lodashGet(transaction, 'receipt.state') !== CONST.IOU.RECEIPT_STATE.OPEN) { updatedTransaction.receipt.state = CONST.IOU.RECEIPT_STATE.OPEN; } @@ -151,6 +156,7 @@ function getUpdatedTransaction(transaction, transactionChanges, isFromExpenseRep ...(_.has(transactionChanges, 'amount') && {amount: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), ...(_.has(transactionChanges, 'currency') && {currency: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), ...(_.has(transactionChanges, 'merchant') && {merchant: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), + ...(_.has(transactionChanges, 'category') && {category: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), }; return updatedTransaction; @@ -232,12 +238,18 @@ function getMerchant(transaction) { } /** - * Return the category from the transaction. This "category" field has no "modified" complement. + * Return the category from the transaction, return the modifiedCategory if present. * * @param {Object} transaction * @return {String} */ function getCategory(transaction) { + const modifiedCategory = lodashGet(transaction, 'modifiedCategory', null); + + if (!_.isNull(modifiedCategory)) { + return modifiedCategory; + } + return lodashGet(transaction, 'category', ''); } diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 8f18119203be..3e23bb596eee 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -1121,6 +1121,7 @@ function editMoneyRequest(transactionID, transactionThreadReportID, transactionC created: null, currency: null, merchant: null, + category: null, }, }, }, @@ -1156,6 +1157,18 @@ function editMoneyRequest(transactionID, transactionThreadReportID, transactionC }, ]; + // STEP 5: Use the modifiedCategory as a category on success + if (!_.isUndefined(updatedTransaction.modifiedCategory)) { + successData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, + value: { + category: updatedTransaction.modifiedCategory, + modifiedCategory: null, + }, + }); + } + // STEP 6: Call the API endpoint const {created, amount, currency, comment, merchant, category} = ReportUtils.getTransactionDetails(updatedTransaction); API.write( From 30c7499a5004cb5247ff16566cf7abce32beb258 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Fri, 15 Sep 2023 00:15:56 +0700 Subject: [PATCH 043/151] Ensure pusher's leavingStatus be sent earlier --- src/libs/actions/Report.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index f0ac66923002..92dbb0e5e0f6 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -1846,6 +1846,12 @@ function getCurrentUserAccountID() { function leaveRoom(reportID) { const report = lodashGet(allReports, [reportID], {}); const reportKeys = _.keys(report); + + // Pusher's leavingStatus should be sent earlier. + // Place the broadcast before calling the LeaveRoom API to prevent a race condition + // between Onyx report being null and Pusher's leavingStatus becoming true. + broadcastUserIsLeavingRoom(reportID); + API.write( 'LeaveRoom', { @@ -1887,7 +1893,6 @@ function leaveRoom(reportID) { if (Navigation.getTopmostReportId() === reportID) { Navigation.goBack(); } - broadcastUserIsLeavingRoom(reportID); navigateToConciergeChat(); } From b9a001eb84adf3a75be054f07d30889a4bc3910e Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Fri, 15 Sep 2023 00:44:57 +0700 Subject: [PATCH 044/151] Add prevUserLeavingStatus to the check --- src/pages/home/ReportScreen.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index ed29ccbb5954..b784c91b23db 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -146,6 +146,7 @@ function ReportScreen({ const flatListRef = useRef(); const reactionListRef = useRef(); const prevReport = usePrevious(report); + const prevUserLeavingStatus = usePrevious(userLeavingStatus); const [skeletonViewContainerHeight, setSkeletonViewContainerHeight] = useState(0); const [isBannerVisible, setIsBannerVisible] = useState(true); @@ -293,7 +294,7 @@ function ReportScreen({ // Navigate to the Concierge chat if the room was removed from another device (e.g. user leaving a room) if ( // non-optimistic case - userLeavingStatus || + (!prevUserLeavingStatus && userLeavingStatus) || // optimistic case (prevOnyxReportID && prevOnyxReportID === routeReportID && !onyxReportID && prevReport.statusNum === CONST.REPORT.STATUS.OPEN && report.statusNum === CONST.REPORT.STATUS.CLOSED) ) { @@ -312,7 +313,7 @@ function ReportScreen({ fetchReportIfNeeded(); ComposerActions.setShouldShowComposeInput(true); - }, [route, report, errors, fetchReportIfNeeded, prevReport.reportID, userLeavingStatus, prevReport.statusNum]); + }, [route, report, errors, fetchReportIfNeeded, prevReport.reportID, prevUserLeavingStatus, userLeavingStatus, prevReport.statusNum]); useEffect(() => { // Ensures subscription event succeeds when the report/workspace room is created optimistically. From 7edcbbeff0207a614188090ef812cbfca1050938 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Fri, 15 Sep 2023 03:25:02 +0530 Subject: [PATCH 045/151] fix: autoFocus for payment input in safari. Signed-off-by: Krishna Gupta --- src/libs/Navigation/OnyxTabNavigator.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/libs/Navigation/OnyxTabNavigator.js b/src/libs/Navigation/OnyxTabNavigator.js index dc68021bf515..56b820f5e05b 100644 --- a/src/libs/Navigation/OnyxTabNavigator.js +++ b/src/libs/Navigation/OnyxTabNavigator.js @@ -4,6 +4,8 @@ import {withOnyx} from 'react-native-onyx'; import PropTypes from 'prop-types'; import Tab from '../actions/Tab'; import ONYXKEYS from '../../ONYXKEYS'; +import * as Browser from '../Browser'; +import CONST from '../../CONST'; const propTypes = { /* ID of the tab component to be saved in onyx */ @@ -23,6 +25,10 @@ const defaultProps = { // eslint-disable-next-line rulesdir/no-inline-named-export export const TopTab = createMaterialTopTabNavigator(); +// Will set this for all platforms once issue below is fixed for native devices. +// https://github.com/Expensify/App/issues/27117 +const keyboardDismissModeProp = Browser.getBrowser() === CONST.BROWSER.SAFARI ? {keyboardDismissMode: 'none'} : {}; + // This takes all the same props as MaterialTopTabsNavigator: https://reactnavigation.org/docs/material-top-tab-navigator/#props, // except ID is now required, and it gets a `selectedTab` from Onyx function OnyxTabNavigator({id, selectedTab, children, ...rest}) { @@ -30,6 +36,8 @@ function OnyxTabNavigator({id, selectedTab, children, ...rest}) { Date: Fri, 15 Sep 2023 17:35:06 +0200 Subject: [PATCH 046/151] do not stop smart scan --- src/libs/TransactionUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/TransactionUtils.js b/src/libs/TransactionUtils.js index 73ba6a8116ef..5fc6294aca91 100644 --- a/src/libs/TransactionUtils.js +++ b/src/libs/TransactionUtils.js @@ -143,7 +143,7 @@ function getUpdatedTransaction(transaction, transactionChanges, isFromExpenseRep if (_.has(transactionChanges, 'category')) { updatedTransaction.modifiedCategory = transactionChanges.category; - shouldStopSmartscan = true; + shouldStopSmartscan = false; } if (shouldStopSmartscan && _.has(transaction, 'receipt') && !_.isEmpty(transaction.receipt) && lodashGet(transaction, 'receipt.state') !== CONST.IOU.RECEIPT_STATE.OPEN) { From 636924f0928a253fd6a7efb4d271eba125afdcbc Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 15 Sep 2023 19:07:05 +0200 Subject: [PATCH 047/151] handle a selected category alone --- src/libs/OptionsListUtils.js | 12 ++++++++++++ tests/unit/OptionsListUtilsTest.js | 23 ++++++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 7629a1acc0a6..8027a30b34a1 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -666,6 +666,18 @@ function getCategoryListSections(categories, recentlyUsedCategories, selectedOpt const numberOfCategories = _.size(categoriesValues); let indexOffset = 0; + if (numberOfCategories === 0 && selectedOptions.length > 0) { + categorySections.push({ + // "Selected" section + title: '', + shouldShow: false, + indexOffset, + data: getCategoryOptionTree(selectedOptions, true), + }); + + return categorySections; + } + if (!_.isEmpty(searchInputValue)) { const searchCategories = _.filter(categoriesValues, (category) => category.name.toLowerCase().includes(searchInputValue.toLowerCase())); diff --git a/tests/unit/OptionsListUtilsTest.js b/tests/unit/OptionsListUtilsTest.js index f1a251e4e433..ef90c767175e 100644 --- a/tests/unit/OptionsListUtilsTest.js +++ b/tests/unit/OptionsListUtilsTest.js @@ -660,6 +660,7 @@ describe('OptionsListUtils', () => { const selectedOptions = [ { name: 'Medical', + enabled: true, }, ]; const smallCategoriesList = { @@ -817,7 +818,7 @@ describe('OptionsListUtils', () => { keyForList: 'Medical', searchText: 'Medical', tooltipText: 'Medical', - isDisabled: true, + isDisabled: false, }, ], }, @@ -1000,6 +1001,23 @@ describe('OptionsListUtils', () => { data: [], }, ]; + const emptyCategoriesList = {}; + const emptySelectedResultList = [ + { + title: '', + shouldShow: false, + indexOffset: 0, + data: [ + { + text: 'Medical', + keyForList: 'Medical', + searchText: 'Medical', + tooltipText: 'Medical', + isDisabled: false, + }, + ], + }, + ]; const smallResult = OptionsListUtils.getNewChatOptions(REPORTS, PERSONAL_DETAILS, [], emptySearch, [], [], false, false, true, smallCategoriesList); expect(smallResult.categoryOptions).toStrictEqual(smallResultList); @@ -1054,6 +1072,9 @@ describe('OptionsListUtils', () => { recentlyUsedCategories, ); expect(largeWrongSearchResult.categoryOptions).toStrictEqual(largeWrongSearchResultList); + + const emptyResult = OptionsListUtils.getNewChatOptions(REPORTS, PERSONAL_DETAILS, [], search, selectedOptions, [], false, false, true, emptyCategoriesList); + expect(emptyResult.categoryOptions).toStrictEqual(emptySelectedResultList); }); it('getCategoryOptionTree()', () => { From ad08c251b2a62e4ce7566fcbf832860044d47609 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Sat, 16 Sep 2023 13:28:55 +0530 Subject: [PATCH 048/151] fix: removed check for safari only. Signed-off-by: Krishna Gupta --- src/libs/Navigation/OnyxTabNavigator.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/libs/Navigation/OnyxTabNavigator.js b/src/libs/Navigation/OnyxTabNavigator.js index 56b820f5e05b..2782054497b0 100644 --- a/src/libs/Navigation/OnyxTabNavigator.js +++ b/src/libs/Navigation/OnyxTabNavigator.js @@ -4,8 +4,6 @@ import {withOnyx} from 'react-native-onyx'; import PropTypes from 'prop-types'; import Tab from '../actions/Tab'; import ONYXKEYS from '../../ONYXKEYS'; -import * as Browser from '../Browser'; -import CONST from '../../CONST'; const propTypes = { /* ID of the tab component to be saved in onyx */ @@ -25,10 +23,6 @@ const defaultProps = { // eslint-disable-next-line rulesdir/no-inline-named-export export const TopTab = createMaterialTopTabNavigator(); -// Will set this for all platforms once issue below is fixed for native devices. -// https://github.com/Expensify/App/issues/27117 -const keyboardDismissModeProp = Browser.getBrowser() === CONST.BROWSER.SAFARI ? {keyboardDismissMode: 'none'} : {}; - // This takes all the same props as MaterialTopTabsNavigator: https://reactnavigation.org/docs/material-top-tab-navigator/#props, // except ID is now required, and it gets a `selectedTab` from Onyx function OnyxTabNavigator({id, selectedTab, children, ...rest}) { @@ -36,11 +30,10 @@ function OnyxTabNavigator({id, selectedTab, children, ...rest}) { { const state = event.data.state; From 812ea0568c93ef608a43f35013a0a9bef6eff8f5 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 18 Sep 2023 11:05:03 +0200 Subject: [PATCH 049/151] do not use modified category --- src/libs/TransactionUtils.js | 11 ++--------- src/libs/actions/IOU.js | 12 ------------ 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/src/libs/TransactionUtils.js b/src/libs/TransactionUtils.js index 5fc6294aca91..7e768e97b837 100644 --- a/src/libs/TransactionUtils.js +++ b/src/libs/TransactionUtils.js @@ -142,8 +142,7 @@ function getUpdatedTransaction(transaction, transactionChanges, isFromExpenseRep } if (_.has(transactionChanges, 'category')) { - updatedTransaction.modifiedCategory = transactionChanges.category; - shouldStopSmartscan = false; + updatedTransaction.category = transactionChanges.category; } if (shouldStopSmartscan && _.has(transaction, 'receipt') && !_.isEmpty(transaction.receipt) && lodashGet(transaction, 'receipt.state') !== CONST.IOU.RECEIPT_STATE.OPEN) { @@ -238,18 +237,12 @@ function getMerchant(transaction) { } /** - * Return the category from the transaction, return the modifiedCategory if present. + * Return the category from the transaction. This "category" field has no "modified" complement. * * @param {Object} transaction * @return {String} */ function getCategory(transaction) { - const modifiedCategory = lodashGet(transaction, 'modifiedCategory', null); - - if (!_.isNull(modifiedCategory)) { - return modifiedCategory; - } - return lodashGet(transaction, 'category', ''); } diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 16722d4dc70b..9f1d67d1db1d 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -1160,18 +1160,6 @@ function editMoneyRequest(transactionID, transactionThreadReportID, transactionC }, ]; - // STEP 5: Use the modifiedCategory as a category on success - if (!_.isUndefined(updatedTransaction.modifiedCategory)) { - successData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, - value: { - category: updatedTransaction.modifiedCategory, - modifiedCategory: null, - }, - }); - } - // STEP 6: Call the API endpoint const {created, amount, currency, comment, merchant, category} = ReportUtils.getTransactionDetails(updatedTransaction); API.write( From d9595b03823b72ea492ac1d0c5ac09f9c2284b1d Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Mon, 18 Sep 2023 15:14:16 +0530 Subject: [PATCH 050/151] fix: Refocus issue on address field on delete and cancel Signed-off-by: Krishna Gupta --- src/components/HeaderWithBackButton/index.js | 2 ++ src/components/PopoverMenu/index.js | 5 +++++ src/components/ThreeDotsMenu/index.js | 12 ++++++++++-- src/pages/iou/WaypointEditor.js | 11 ++++++++++- 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/components/HeaderWithBackButton/index.js b/src/components/HeaderWithBackButton/index.js index bbf905cc1ac2..b99290918e13 100755 --- a/src/components/HeaderWithBackButton/index.js +++ b/src/components/HeaderWithBackButton/index.js @@ -47,6 +47,7 @@ function HeaderWithBackButton({ }, threeDotsMenuItems = [], children = null, + onPopoverHide = () => {}, }) { const [isDownloadButtonActive, temporarilyDisableDownloadButton] = useThrottledButtonState(); const {translate} = useLocalize(); @@ -137,6 +138,7 @@ function HeaderWithBackButton({ menuItems={threeDotsMenuItems} onIconPress={onThreeDotsButtonPress} anchorPosition={threeDotsAnchorPosition} + onPopoverHide={onPopoverHide} /> )} {shouldShowCloseButton && ( diff --git a/src/components/PopoverMenu/index.js b/src/components/PopoverMenu/index.js index 5fabf73547ea..1533f329ad4a 100644 --- a/src/components/PopoverMenu/index.js +++ b/src/components/PopoverMenu/index.js @@ -34,6 +34,9 @@ const propTypes = { }), withoutOverlay: PropTypes.bool, + + /** Function to call on popover hide */ + onPopoverHide: PropTypes.func, }; const defaultProps = { @@ -44,6 +47,7 @@ const defaultProps = { }, anchorRef: () => {}, withoutOverlay: false, + onPopoverHide: () => {}, }; function PopoverMenu(props) { @@ -78,6 +82,7 @@ function PopoverMenu(props) { isVisible={props.isVisible} onModalHide={() => { setFocusedIndex(-1); + props.onPopoverHide(); if (selectedItemIndex.current !== null) { props.menuItems[selectedItemIndex.current].onSelected(); selectedItemIndex.current = null; diff --git a/src/components/ThreeDotsMenu/index.js b/src/components/ThreeDotsMenu/index.js index b5637a4f3879..5daeb9669933 100644 --- a/src/components/ThreeDotsMenu/index.js +++ b/src/components/ThreeDotsMenu/index.js @@ -1,5 +1,5 @@ import React, {useState, useRef} from 'react'; -import {View} from 'react-native'; +import {InteractionManager, View} from 'react-native'; import PropTypes from 'prop-types'; import _ from 'underscore'; import Icon from '../Icon'; @@ -45,6 +45,9 @@ const propTypes = { horizontal: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL)), vertical: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_VERTICAL)), }), + + /** Function to call on popover hide */ + onPopoverClose: PropTypes.func, }; const defaultProps = { @@ -57,9 +60,10 @@ const defaultProps = { horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP, // we assume that popover menu opens below the button, anchor is at TOP }, + onPopoverHide: () => {}, }; -function ThreeDotsMenu({iconTooltip, icon, iconFill, iconStyles, onIconPress, menuItems, anchorPosition, anchorAlignment}) { +function ThreeDotsMenu({iconTooltip, icon, iconFill, iconStyles, onIconPress, menuItems, anchorPosition, anchorAlignment, onPopoverHide}) { const [isPopupMenuVisible, setPopupMenuVisible] = useState(false); const buttonRef = useRef(null); const {translate} = useLocalize(); @@ -69,6 +73,9 @@ function ThreeDotsMenu({iconTooltip, icon, iconFill, iconStyles, onIconPress, me }; const hidePopoverMenu = () => { + InteractionManager.runAfterInteractions(() => { + onPopoverHide(); + }); setPopupMenuVisible(false); }; @@ -101,6 +108,7 @@ function ThreeDotsMenu({iconTooltip, icon, iconFill, iconStyles, onIconPress, me { + InteractionManager.runAfterInteractions(() => { + if (!textInput.current) return; + textInput.current.focus(); + }); + }; + return ( setIsDeleteStopModalOpen(true), }, ]} + onPopoverHide={focus} /> setIsDeleteStopModalOpen(false)} + onModalHide={focus} prompt={translate('distance.deleteWaypointConfirmation')} confirmText={translate('common.delete')} cancelText={translate('common.cancel')} From 6756074fa9163d0367e8e60d4cc21c2dbc73eff2 Mon Sep 17 00:00:00 2001 From: Eduardo Date: Mon, 18 Sep 2023 13:18:39 +0200 Subject: [PATCH 051/151] Updated script to handle styles with theme function, and obj spreading in style.js file --- .github/scripts/findUnusedKeys.sh | 154 ++++++++++++++++++++++++------ 1 file changed, 123 insertions(+), 31 deletions(-) diff --git a/.github/scripts/findUnusedKeys.sh b/.github/scripts/findUnusedKeys.sh index 85d8112e8f13..e34d8e0d78ea 100755 --- a/.github/scripts/findUnusedKeys.sh +++ b/.github/scripts/findUnusedKeys.sh @@ -110,6 +110,85 @@ lookfor_unused_keywords() { find_styles_object_and_store_keys() { local file="$1" local base_name="${2:-styles}" # Set styles as default + local line_number=0 + local inside_arrow_function=false + + while IFS= read -r line; do + ((line_number++)) + + # Check if we are inside an arrow function and we find a closing curly brace + if [[ "$inside_arrow_function" == true ]]; then + if [[ "$line" =~ ^[[:space:]]*\}\) ]]; then + inside_arrow_function=false + fi + continue + fi + + # Check if we are inside an arrow function + if [[ "$line" =~ ^[[:space:]]*([a-zA-Z0-9_-])+:[[:space:]]*\(.*\)[[:space:]]*'=>'[[:space:]]*\(\{ || "$line" =~ ^[[:space:]]*(const|let|var)[[:space:]]+([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*\(.*\)[[:space:]]*'=>' ]]; then + inside_arrow_function=true + continue + fi + + # Skip lines that are not key-related + if [[ ! "$line" =~ ^[[:space:]]*(const|let|var)[[:space:]]+([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*\{|^[[:space:]]*([a-zA-Z0-9_-]+\.)?[a-zA-Z0-9_-]+:[[:space:]]*\{|^[[:space:]]*\} ]]; then + continue + fi + + if [[ "$line" =~ ^[[:space:]]*(const|let|var)[[:space:]]+([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*\{ ]]; then + key="${BASH_REMATCH[2]%%:*{*)}" + echo "styles.${key}|...${key}|${base_name}.${key}:${file}:${line_number}" >> "$STYLES_KEYS_FILE" + fi + done < "$file" +} + +find_styles_functions_and_store_keys() { + local file="$1" + local line_number=0 + local inside_object=false + local inside_arrow_function=false + local key="" + + while IFS= read -r line; do + ((line_number++)) + + # Skip lines that are not key-related + if [[ "${line}" == *styles* ]]; then + continue + fi + + # Check if we are inside an arrow function and we find a closing curly brace + if [[ "$inside_arrow_function" == true ]]; then + if [[ "$line" =~ ^[[:space:]]*\}\) ]]; then + inside_arrow_function=false + fi + continue + fi + + # Check if we are inside an arrow function + if [[ "${line}" =~ ^[[:space:]]*([a-zA-Z0-9_-])+:[[:space:]]*\(.*\)[[:space:]]*'=>'[[:space:]]*\( ]]; then + inside_arrow_function=true + key="${line%%:*}" + key="${key// /}" # Trim spaces + echo "styles.${key}|...${key}:${file}:${line_number}" >> "$STYLES_KEYS_FILE" + continue + fi + + if [[ "$line" =~ ^[[:space:]]*(const|let|var)[[:space:]]+([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*\(.*\)[[:space:]]*'=>' ]]; then + inside_arrow_function=true + key="${BASH_REMATCH[2]}" + key="${key// /}" # Trim spaces + echo "styles.${key}|...${key}:${file}:${line_number}" >> "$STYLES_KEYS_FILE" + continue + fi + + done < "$file" +} + +find_theme_style_and_store_keys() { + local file="$1" + local start_line_number="$2" + local base_name="${3:-styles}" # Set styles as default local parent_keys=() local root_key="" local line_number=0 @@ -118,6 +197,10 @@ find_styles_object_and_store_keys() { while IFS= read -r line; do ((line_number++)) + if [ ! "$line_number" -ge "$start_line_number" ]; then + continue + fi + # Check if we are inside an arrow function and we find a closing curly brace if [[ "$inside_arrow_function" == true ]]; then if [[ "$line" =~ ^[[:space:]]*\}\) ]]; then @@ -127,22 +210,22 @@ find_styles_object_and_store_keys() { fi # Check if we are inside an arrow function - if [[ "$line" =~ ^[[:space:]]*([a-zA-Z0-9_-])+:[[:space:]]*\(.*\)[[:space:]]*'=>'[[:space:]]*\(\{ ]]; then + if [[ "$line" =~ ^[[:space:]]*([a-zA-Z0-9_-])+:[[:space:]]*\(.*\)[[:space:]]*'=>'[[:space:]]*\(\{ || "$line" =~ ^[[:space:]]*(const|let|var)[[:space:]]+([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*\(.*\)[[:space:]]*'=>' ]]; then inside_arrow_function=true continue fi # Skip lines that are not key-related if [[ ! "$line" =~ ^[[:space:]]*(const|let|var)[[:space:]]+([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*\{|^[[:space:]]*([a-zA-Z0-9_-]+\.)?[a-zA-Z0-9_-]+:[[:space:]]*\{|^[[:space:]]*\} ]]; then + continue fi - if [[ "$line" =~ ^[[:space:]]*(const|let|var)[[:space:]]+([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*\{ ]]; then - root_key="${BASH_REMATCH[2]%%:*{*)}" - elif [[ "$line" =~ ^[[:space:]]*([a-zA-Z0-9_-]+\.)?[a-zA-Z0-9_-]+:[[:space:]]*\{|^[[:space:]]*([a-zA-Z0-9_-])+:[[:space:]]*\(.*\)[[:space:]]*'=>'[[:space:]]*\(\{ ]]; then + if [[ "$line" =~ ^[[:space:]]*([a-zA-Z0-9_-]+\.)?[a-zA-Z0-9_-]+:[[:space:]]*\{|^[[:space:]]*([a-zA-Z0-9_-])+:[[:space:]]*\(.*\)[[:space:]]*'=>'[[:space:]]*\(\{ ]]; then # Removing all the extra lines after the ":" local key="${line%%:*}" key="${key// /}" # Trim spaces + if [[ ${#parent_keys[@]} -gt 0 ]]; then local parent_key_trimmed="${parent_keys[${#parent_keys[@]}-1]// /}" # Trim spaces key="$parent_key_trimmed.$key" @@ -150,12 +233,8 @@ find_styles_object_and_store_keys() { local parent_key_trimmed="${root_key// /}" # Trim spaces key="$parent_key_trimmed.$key" fi - - if [[ "$key" == "styles."* ]]; then - echo "${key}:${file}:${line_number}" >> "$STYLES_KEYS_FILE" - else - echo "styles.${key}|${base_name}.${key}:${file}:${line_number}" >> "$STYLES_KEYS_FILE" - fi + + echo "styles.${key}|...${key}|${base_name}.${key}:${file}:${line_number}" >> "$STYLES_KEYS_FILE" parent_keys+=("$key") elif [[ "$line" =~ ^[[:space:]]*\} ]]; then parent_keys=("${parent_keys[@]:0:${#parent_keys[@]}-1}") @@ -163,40 +242,51 @@ find_styles_object_and_store_keys() { done < "$file" } -find_styles_functions_and_store_keys() { +# Given that all the styles are inside of a function, we need to find the function and then look for the styles +collect_theme_keys_from_styles() { local file="$1" local line_number=0 - local inside_object=false - local inside_arrow_function=false - local key="" + local inside_styles=false while IFS= read -r line; do ((line_number++)) - # Check if we are inside an arrow function - if [[ "$line" =~ ^[[:space:]]*([a-zA-Z0-9_-])+:[[:space:]]*\(.*\)[[:space:]]*'=>'[[:space:]]*\(\{ ]]; then - inside_arrow_function=true - key="${line%%:*}" - key="${key// /}" # Trim spaces - echo "styles.${key}:${file}:${line_number}" >> "$STYLES_KEYS_FILE" + if [[ "$inside_styles" == false ]]; then + if [[ "$line" =~ ^[[:space:]]*(const|let|var)[[:space:]]+([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*\(.*\)[[:space:]]*'=>' ]]; then + key="${BASH_REMATCH[2]}" + key="${key// /}" # Trim spaces + if [[ "$key" == "styles"* ]]; then + inside_styles=true + # Need to start within the style function + ((line_number++)) + find_theme_style_and_store_keys "$file" "$line_number" + fi + continue + fi fi + done < "$file" +} - # If we are inside an arrow function and we find an opening curly brace, - # then set inside_object to true, indicating we are inside an object. - if [[ "$inside_arrow_function" == true && "$line" =~ ^[[:space:]]*\{ ]]; then - inside_object=true - fi +lookfor_unused_spread_keywords() { + local inside_object=false - # If we are inside an object, continue to the next line. - if [[ "$inside_object" == true ]]; then - continue + while IFS= read -r line; do + # Detect the start of an object + if [[ "$line" =~ ^[[:space:]]*([a-zA-Z0-9_-]+\.)?[a-zA-Z0-9_-]+:[[:space:]]*\{ ]]; then + inside_object=true fi - # If we find a closing curly brace, reset the inside_object flag. + # Detect the end of an object if [[ "$line" =~ ^[[:space:]]*\},?$ ]]; then inside_object=false fi - done < "$file" + + # If we're inside an object and the line starts with '...', capture the key + if [[ "$inside_object" == true && "$line" =~ ^[[:space:]]*\.\.\.([a-zA-Z0-9_]+)(\(.+\))?,?$ ]]; then + local key=("${BASH_REMATCH[1]}") + remove_keyword "...${key}" + fi + done < "$STYLES_FILE" } find_utility_styles_store_prefix() { @@ -228,7 +318,7 @@ find_utility_usage_as_styles() { continue fi - find_styles_object_and_store_keys "${file}" "${root_key}" + find_theme_style_and_store_keys "${file}" 0 "${root_key}" done < <(find "${UTILITIES_STYLES_FILE}" -type f \( "${FILE_EXTENSIONS[@]}" \)) } @@ -260,11 +350,13 @@ find_utility_usage_as_styles # Find and store keys from styles.js find_styles_object_and_store_keys "$STYLES_FILE" find_styles_functions_and_store_keys "$STYLES_FILE" +collect_theme_keys_from_styles "$STYLES_FILE" echo "🗄️ Now going through the codebase and looking for unused keys." # Look for usages of utilities into src/styles lookfor_unused_utilities +lookfor_unused_spread_keywords lookfor_unused_keywords final_styles_line_count=$(count_lines "$STYLES_KEYS_FILE") From 54e786c1593e5a53ba9c58827c345908be6420af Mon Sep 17 00:00:00 2001 From: Eduardo Date: Mon, 18 Sep 2023 13:36:08 +0200 Subject: [PATCH 052/151] fixed some lint issues --- .github/scripts/findUnusedKeys.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/scripts/findUnusedKeys.sh b/.github/scripts/findUnusedKeys.sh index e34d8e0d78ea..6668d3257546 100755 --- a/.github/scripts/findUnusedKeys.sh +++ b/.github/scripts/findUnusedKeys.sh @@ -259,7 +259,7 @@ collect_theme_keys_from_styles() { inside_styles=true # Need to start within the style function ((line_number++)) - find_theme_style_and_store_keys "$file" "$line_number" + find_theme_style_and_store_keys "$STYLES_FILE" "$line_number" fi continue fi @@ -269,6 +269,7 @@ collect_theme_keys_from_styles() { lookfor_unused_spread_keywords() { local inside_object=false + local key="" while IFS= read -r line; do # Detect the start of an object @@ -283,7 +284,7 @@ lookfor_unused_spread_keywords() { # If we're inside an object and the line starts with '...', capture the key if [[ "$inside_object" == true && "$line" =~ ^[[:space:]]*\.\.\.([a-zA-Z0-9_]+)(\(.+\))?,?$ ]]; then - local key=("${BASH_REMATCH[1]}") + key="${BASH_REMATCH[1]}" remove_keyword "...${key}" fi done < "$STYLES_FILE" From 3dd7b5c14b2817594ef19f299b329262174ec61a Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 18 Sep 2023 15:11:26 +0200 Subject: [PATCH 053/151] create getRootParentReport helper --- src/libs/ReportUtils.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 9396ea921b61..7115de23d1a1 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1601,6 +1601,29 @@ function getParentReport(report) { return lodashGet(allReports, `${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`, {}); } +/** + * Returns the root parentReport if the given report is nested. + * Uses recursion to iterate any depth of nested reports. + * + * @param {Object} report + * @returns {Object} + */ +function getRootParentReport(report) { + if (!report) { + return {}; + } + + // Returns the founded root report, because it does not have a parentReportID + if (!report.parentReportID) { + return report; + } + + const parentReport = getReport(report.parentReportID); + + // Runs recursion to iterate a parent report + return getRootParentReport(parentReport); +} + /** * Get the title for a report. * @@ -3613,6 +3636,7 @@ export { isAllowedToComment, getBankAccountRoute, getParentReport, + getRootParentReport, getTaskParentReportActionIDInAssigneeReport, getReportPreviewMessage, getModifiedExpenseMessage, From d7b6b89b73d20413d809093843fa48941862921e Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 18 Sep 2023 15:12:17 +0200 Subject: [PATCH 054/151] improve conditions of MoneyRequestConfirmationList --- src/components/MoneyRequestConfirmationList.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index da98d324681e..d72860e78a48 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -191,13 +191,19 @@ function MoneyRequestConfirmationList(props) { const {unit, rate, currency} = props.mileageRate; const distance = lodashGet(transaction, 'routes.route0.distance', 0); const shouldCalculateDistanceAmount = props.isDistanceRequest && props.iouAmount === 0; - const shouldCategoryBeEditable = !_.isEmpty(props.policyCategories) && Permissions.canUseCategories(props.betas); + + // A flag for verifying that the current report is a sub-report of a workspace chat + const isPolicyExpenseChat = useMemo(() => ReportUtils.isPolicyExpenseChat(ReportUtils.getRootParentReport(ReportUtils.getReport(props.reportID))), [props.reportID]); + + // A flag for showing the categories field + const shouldShowCategories = isPolicyExpenseChat && Permissions.canUseCategories(props.betas) && !_.isEmpty(props.policyCategories); // Fetches the first tag list of the policy const tagListKey = _.first(_.keys(props.policyTags)); const tagList = lodashGet(props.policyTags, [tagListKey, 'tags'], []); const tagListName = lodashGet(props.policyTags, [tagListKey, 'name'], ''); - const canUseTags = Permissions.canUseTags(props.betas); + // A flag for showing the tags field + const shouldShowTags = isPolicyExpenseChat && Permissions.canUseTags(props.betas) && !_.isEmpty(tagList); const formattedAmount = CurrencyUtils.convertToDisplayString( shouldCalculateDistanceAmount ? DistanceRequestUtils.getDistanceRequestAmount(distance, unit, rate) : props.iouAmount, @@ -510,7 +516,7 @@ function MoneyRequestConfirmationList(props) { disabled={didConfirm || props.isReadOnly || !isTypeRequest} /> )} - {shouldCategoryBeEditable && ( + {shouldShowCategories && ( )} - {canUseTags && !!tagList && ( + {shouldShowTags && ( Date: Mon, 18 Sep 2023 15:14:09 +0200 Subject: [PATCH 055/151] improve conditions of MoneyRequestView --- .../ReportActionItem/MoneyRequestView.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index eb08639d8d8e..8232f8618f2c 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useMemo} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; @@ -10,6 +10,7 @@ import ROUTES from '../../ROUTES'; import Navigation from '../../libs/Navigation/Navigation'; import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsPropTypes} from '../withCurrentUserPersonalDetails'; import compose from '../../libs/compose'; +import Permissions from '../../libs/Permissions'; import MenuItemWithTopDescription from '../MenuItemWithTopDescription'; import styles from '../../styles/styles'; import * as ReportUtils from '../../libs/ReportUtils'; @@ -31,6 +32,9 @@ import OfflineWithFeedback from '../OfflineWithFeedback'; import categoryPropTypes from '../categoryPropTypes'; const propTypes = { + /** List of betas available to current user */ + betas: PropTypes.arrayOf(PropTypes.string), + /** The report currently being looked at */ report: reportPropTypes.isRequired, @@ -51,6 +55,7 @@ const propTypes = { }; const defaultProps = { + betas: [], parentReport: {}, policyCategories: {}, transaction: { @@ -60,7 +65,7 @@ const defaultProps = { }, }; -function MoneyRequestView({report, parentReport, policyCategories, shouldShowHorizontalRule, transaction}) { +function MoneyRequestView({betas, report, parentReport, policyCategories, shouldShowHorizontalRule, transaction}) { const {isSmallScreenWidth} = useWindowDimensions(); const {translate} = useLocalize(); @@ -80,7 +85,10 @@ function MoneyRequestView({report, parentReport, policyCategories, shouldShowHor const isSettled = ReportUtils.isSettled(moneyRequestReport.reportID); const canEdit = ReportUtils.canEditMoneyRequest(parentReportAction); - const shouldShowCategory = !_.isEmpty(policyCategories) || !_.isEmpty(transactionCategory); + // A flag for verifying that the current report is a sub-report of a workspace chat + const isPolicyExpenseChat = useMemo(() => ReportUtils.isPolicyExpenseChat(ReportUtils.getRootParentReport(report)), [report]); + // A flag for showing categories + const shouldShowCategory = isPolicyExpenseChat && Permissions.canUseCategories(betas) && (!_.isEmpty(policyCategories) || !_.isEmpty(transactionCategory)); let description = `${translate('iou.amount')} • ${translate('iou.cash')}`; if (isSettled) { @@ -201,6 +209,9 @@ MoneyRequestView.displayName = 'MoneyRequestView'; export default compose( withCurrentUserPersonalDetails, withOnyx({ + betas: { + key: ONYXKEYS.BETAS, + }, parentReport: { key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`, }, From 24e4db5626db5a0e29238565240fa7f5de913e1d Mon Sep 17 00:00:00 2001 From: Eduardo Date: Mon, 18 Sep 2023 16:03:35 +0200 Subject: [PATCH 056/151] removed unused style --- src/styles/styles.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/styles/styles.js b/src/styles/styles.js index a8b764dc0e39..81f3c22c0dae 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -225,11 +225,6 @@ const styles = (theme) => ({ color: theme.textSupporting, }, - appIconBorderRadius: { - overflow: 'hidden', - borderRadius: 12, - }, - webViewStyles: webViewStyles(theme), link: link(theme), From f8b84a96161b61712069800d54da4312300ead1a Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 18 Sep 2023 18:21:04 +0200 Subject: [PATCH 057/151] improve options helpers --- src/libs/OptionsListUtils.js | 38 +++++++++++++- tests/unit/OptionsListUtilsTest.js | 84 ------------------------------ 2 files changed, 37 insertions(+), 85 deletions(-) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 8027a30b34a1..c38f99edac65 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -599,6 +599,32 @@ function isCurrentUser(userDetails) { return _.some(_.keys(loginList), (login) => login.toLowerCase() === userDetailsLogin.toLowerCase()); } +/** + * Calculates count of all enabled options + * + * @param {Object[]} options - an initial strings array + * @param {Boolean} options[].enabled - a flag to enable/disable option in a list + * @param {String} options[].name - a name of an option + * @returns {Boolean} + */ +function getEnabledCategoriesCount(options) { + return _.chain(options) + .filter((option) => Boolean(option.enabled)) + .value().length; +} + +/** + * Verifies that there is at least one enabled option + * + * @param {Object[]} options - an initial strings array + * @param {Boolean} options[].enabled - a flag to enable/disable option in a list + * @param {String} options[].name - a name of an option + * @returns {Boolean} + */ +function hasEnabledOptions(options) { + return _.some(options, (option) => Boolean(option.enabled)); +} + /** * Build the options for the category tree hierarchy via indents * @@ -612,6 +638,10 @@ function getCategoryOptionTree(options, isOneLine = false) { const optionCollection = {}; _.each(options, (option) => { + if (!option.enabled) { + return; + } + if (isOneLine) { if (_.has(optionCollection, option.name)) { return; @@ -662,7 +692,11 @@ function getCategoryOptionTree(options, isOneLine = false) { */ function getCategoryListSections(categories, recentlyUsedCategories, selectedOptions, searchInputValue, maxRecentReportsToShow) { const categorySections = []; - const categoriesValues = _.values(categories); + const categoriesValues = _.chain(categories) + .values() + .filter((category) => category.enabled) + .value(); + const numberOfCategories = _.size(categoriesValues); let indexOffset = 0; @@ -1342,6 +1376,8 @@ export { isSearchStringMatch, shouldOptionShowTooltip, getLastMessageTextForReport, + getEnabledCategoriesCount, + hasEnabledOptions, getCategoryOptionTree, formatMemberForList, }; diff --git a/tests/unit/OptionsListUtilsTest.js b/tests/unit/OptionsListUtilsTest.js index ef90c767175e..f10eecefaafd 100644 --- a/tests/unit/OptionsListUtilsTest.js +++ b/tests/unit/OptionsListUtilsTest.js @@ -687,13 +687,6 @@ describe('OptionsListUtils', () => { shouldShow: false, indexOffset: 0, data: [ - { - text: 'Taxi', - keyForList: 'Taxi', - searchText: 'Taxi', - tooltipText: 'Taxi', - isDisabled: true, - }, { text: 'Restaurant', keyForList: 'Restaurant', @@ -827,13 +820,6 @@ describe('OptionsListUtils', () => { shouldShow: true, indexOffset: 1, data: [ - { - text: 'Taxi', - keyForList: 'Taxi', - searchText: 'Taxi', - tooltipText: 'Taxi', - isDisabled: true, - }, { text: 'Restaurant', keyForList: 'Restaurant', @@ -848,13 +834,6 @@ describe('OptionsListUtils', () => { shouldShow: true, indexOffset: 3, data: [ - { - text: 'Taxi', - keyForList: 'Taxi', - searchText: 'Taxi', - tooltipText: 'Taxi', - isDisabled: true, - }, { text: 'Restaurant', keyForList: 'Restaurant', @@ -883,13 +862,6 @@ describe('OptionsListUtils', () => { tooltipText: 'Milk', isDisabled: false, }, - { - text: ' Vegetables', - keyForList: 'Vegetables', - searchText: 'Food: Vegetables', - tooltipText: 'Vegetables', - isDisabled: true, - }, { text: 'Cars', keyForList: 'Cars', @@ -904,13 +876,6 @@ describe('OptionsListUtils', () => { tooltipText: 'Audi', isDisabled: false, }, - { - text: ' BMW', - keyForList: 'BMW', - searchText: 'Cars: BMW', - tooltipText: 'BMW', - isDisabled: true, - }, { text: ' Mercedes-Benz', keyForList: 'Mercedes-Benz', @@ -939,13 +904,6 @@ describe('OptionsListUtils', () => { tooltipText: 'Breakfast', isDisabled: false, }, - { - text: ' Dinner', - keyForList: 'Dinner', - searchText: 'Travel: Meals: Dinner', - tooltipText: 'Dinner', - isDisabled: true, - }, { text: ' Lunch', keyForList: 'Lunch', @@ -983,13 +941,6 @@ describe('OptionsListUtils', () => { tooltipText: 'Food: Milk', isDisabled: false, }, - { - text: 'Food: Vegetables', - keyForList: 'Food: Vegetables', - searchText: 'Food: Vegetables', - tooltipText: 'Food: Vegetables', - isDisabled: true, - }, ], }, ]; @@ -1153,13 +1104,6 @@ describe('OptionsListUtils', () => { }, }; const result = [ - { - text: 'Taxi', - keyForList: 'Taxi', - searchText: 'Taxi', - tooltipText: 'Taxi', - isDisabled: true, - }, { text: 'Restaurant', keyForList: 'Restaurant', @@ -1188,13 +1132,6 @@ describe('OptionsListUtils', () => { tooltipText: 'Milk', isDisabled: false, }, - { - text: ' Vegetables', - keyForList: 'Vegetables', - searchText: 'Food: Vegetables', - tooltipText: 'Vegetables', - isDisabled: true, - }, { text: 'Cars', keyForList: 'Cars', @@ -1209,13 +1146,6 @@ describe('OptionsListUtils', () => { tooltipText: 'Audi', isDisabled: false, }, - { - text: ' BMW', - keyForList: 'BMW', - searchText: 'Cars: BMW', - tooltipText: 'BMW', - isDisabled: true, - }, { text: ' Mercedes-Benz', keyForList: 'Mercedes-Benz', @@ -1223,13 +1153,6 @@ describe('OptionsListUtils', () => { tooltipText: 'Mercedes-Benz', isDisabled: false, }, - { - text: 'Medical', - keyForList: 'Medical', - searchText: 'Medical', - tooltipText: 'Medical', - isDisabled: true, - }, { text: 'Travel', keyForList: 'Travel', @@ -1251,13 +1174,6 @@ describe('OptionsListUtils', () => { tooltipText: 'Breakfast', isDisabled: false, }, - { - text: ' Dinner', - keyForList: 'Dinner', - searchText: 'Travel: Meals: Dinner', - tooltipText: 'Dinner', - isDisabled: true, - }, { text: ' Lunch', keyForList: 'Lunch', From b12a93ed40c9d0eb03c8be2c15769123d66ae2c2 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 18 Sep 2023 18:21:18 +0200 Subject: [PATCH 058/151] check enabled categories --- src/components/CategoryPicker/index.js | 2 +- src/components/MoneyRequestConfirmationList.js | 2 +- src/components/ReportActionItem/MoneyRequestView.js | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/CategoryPicker/index.js b/src/components/CategoryPicker/index.js index 66ac0bf14d4a..da7b77ca193e 100644 --- a/src/components/CategoryPicker/index.js +++ b/src/components/CategoryPicker/index.js @@ -14,7 +14,7 @@ function CategoryPicker({selectedCategory, policyCategories, policyRecentlyUsedC const {translate} = useLocalize(); const [searchValue, setSearchValue] = useState(''); - const policyCategoriesCount = _.size(policyCategories); + const policyCategoriesCount = OptionsListUtils.getEnabledCategoriesCount(_.values(policyCategories)); const isCategoriesCountBelowThreshold = policyCategoriesCount < CONST.CATEGORY_LIST_THRESHOLD; const selectedOptions = useMemo(() => { diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index d72860e78a48..7ed5ba0e1f09 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -196,7 +196,7 @@ function MoneyRequestConfirmationList(props) { const isPolicyExpenseChat = useMemo(() => ReportUtils.isPolicyExpenseChat(ReportUtils.getRootParentReport(ReportUtils.getReport(props.reportID))), [props.reportID]); // A flag for showing the categories field - const shouldShowCategories = isPolicyExpenseChat && Permissions.canUseCategories(props.betas) && !_.isEmpty(props.policyCategories); + const shouldShowCategories = isPolicyExpenseChat && Permissions.canUseCategories(props.betas) && OptionsListUtils.hasEnabledOptions(_.values(props.policyCategories)); // Fetches the first tag list of the policy const tagListKey = _.first(_.keys(props.policyTags)); diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index 8232f8618f2c..be937a834c42 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -14,6 +14,7 @@ import Permissions from '../../libs/Permissions'; import MenuItemWithTopDescription from '../MenuItemWithTopDescription'; import styles from '../../styles/styles'; import * as ReportUtils from '../../libs/ReportUtils'; +import * as OptionsListUtils from '../../libs/OptionsListUtils'; import * as ReportActionsUtils from '../../libs/ReportActionsUtils'; import * as StyleUtils from '../../styles/StyleUtils'; import CONST from '../../CONST'; @@ -88,7 +89,8 @@ function MoneyRequestView({betas, report, parentReport, policyCategories, should // A flag for verifying that the current report is a sub-report of a workspace chat const isPolicyExpenseChat = useMemo(() => ReportUtils.isPolicyExpenseChat(ReportUtils.getRootParentReport(report)), [report]); // A flag for showing categories - const shouldShowCategory = isPolicyExpenseChat && Permissions.canUseCategories(betas) && (!_.isEmpty(policyCategories) || !_.isEmpty(transactionCategory)); + const shouldShowCategory = + isPolicyExpenseChat && Permissions.canUseCategories(betas) && (!_.isEmpty(transactionCategory) || OptionsListUtils.hasEnabledOptions(_.values(policyCategories))); let description = `${translate('iou.amount')} • ${translate('iou.cash')}`; if (isSettled) { From 7a643bfdfca602541f070e8ea257e8c462e5808a Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 18 Sep 2023 18:45:44 +0200 Subject: [PATCH 059/151] fix billable condition --- src/components/MoneyRequestConfirmationList.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index da969dce1145..d60b2e5d44bf 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -204,8 +204,12 @@ function MoneyRequestConfirmationList(props) { const tagListKey = _.first(_.keys(props.policyTags)); const tagList = lodashGet(props.policyTags, [tagListKey, 'tags'], []); const tagListName = lodashGet(props.policyTags, [tagListKey, 'name'], ''); + const canUseTags = Permissions.canUseTags(props.betas); // A flag for showing the tags field - const shouldShowTags = isPolicyExpenseChat && Permissions.canUseTags(props.betas) && !_.isEmpty(tagList); + const shouldShowTags = isPolicyExpenseChat && canUseTags && !_.isEmpty(tagList); + + // A flag for showing the billable field + const shouldShowBillable = canUseTags && !lodashGet(props.policy, 'disabledFields.defaultBillable', true); const formattedAmount = CurrencyUtils.convertToDisplayString( shouldCalculateDistanceAmount ? DistanceRequestUtils.getDistanceRequestAmount(distance, unit, rate) : props.iouAmount, @@ -539,7 +543,7 @@ function MoneyRequestConfirmationList(props) { disabled={didConfirm || props.isReadOnly} /> )} - {canUseTags && !lodashGet(props.policy, 'disabledFields.defaultBillable', true) && ( + {shouldShowBillable && ( {translate('common.billable')} Date: Tue, 19 Sep 2023 11:43:57 +0700 Subject: [PATCH 060/151] fix unread marker of modified messsage --- src/libs/actions/IOU.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 587b2392deba..79b1ecdad855 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -1087,6 +1087,7 @@ function editMoneyRequest(transactionID, transactionThreadReportID, transactionC } // STEP 4: Compose the optimistic data + const currentTime = DateUtils.getDBTime(); const optimisticData = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -1110,6 +1111,14 @@ function editMoneyRequest(transactionID, transactionThreadReportID, transactionC key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.chatReportID}`, value: updatedChatReport, }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`, + value: { + lastReadTime: currentTime, + lastVisibleActionCreated: currentTime, + }, + }, ]; const successData = [ @@ -1163,6 +1172,14 @@ function editMoneyRequest(transactionID, transactionThreadReportID, transactionC key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.chatReportID}`, value: chatReport, }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`, + value: { + lastReadTime: transactionThread.lastReadTime, + lastVisibleActionCreated: transactionThread.lastVisibleActionCreated, + }, + }, ]; // STEP 6: Call the API endpoint From 62266bad2e4bf79cc9a960bdeb40fb4697c970d1 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 19 Sep 2023 13:10:10 +0700 Subject: [PATCH 061/151] fix: lint issue --- src/components/ImageView/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ImageView/index.js b/src/components/ImageView/index.js index 5d6d1b609ab3..afc5523c17e9 100644 --- a/src/components/ImageView/index.js +++ b/src/components/ImageView/index.js @@ -225,7 +225,7 @@ function ImageView({isAuthTokenRequired, url, fileName}) { source={{uri: url}} isAuthTokenRequired={isAuthTokenRequired} // Hide image until finished loading to prevent showing preview with wrong dimensions. - style={(isLoading || zoomScale === 0) ? undefined : [styles.w100, styles.h100]} + style={isLoading || zoomScale === 0 ? undefined : [styles.w100, styles.h100]} // When Image dimensions are lower than the container boundary(zoomscale <= 1), use `contain` to render the image with natural dimensions. // Both `center` and `contain` keeps the image centered on both x and y axis. resizeMode={zoomScale > 1 ? Image.resizeMode.center : Image.resizeMode.contain} From 5707b9de7e468ba62ef81e13178092f31d590316 Mon Sep 17 00:00:00 2001 From: Janic Duplessis Date: Sat, 20 May 2023 14:12:19 -0400 Subject: [PATCH 062/151] Increase FlatList batch size --- src/components/InvertedFlatList/BaseInvertedFlatList.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/InvertedFlatList/BaseInvertedFlatList.js b/src/components/InvertedFlatList/BaseInvertedFlatList.js index d3fcda0ea5fd..0327e4e0b5b9 100644 --- a/src/components/InvertedFlatList/BaseInvertedFlatList.js +++ b/src/components/InvertedFlatList/BaseInvertedFlatList.js @@ -136,8 +136,10 @@ class BaseInvertedFlatList extends Component { // Native platforms do not need to measure items and work fine without this. // Web requires that items be measured or else crazy things happen when scrolling. getItemLayout={this.props.shouldMeasureItems ? this.getItemLayout : undefined} - // We keep this property very low so that chat switching remains fast - maxToRenderPerBatch={1} + // Keep batch size relatively small for responsiveness, but not too small as it will cause + // excessive rendering. See https://github.com/Expensify/App/pull/19345 for performance testing + // of this value. + maxToRenderPerBatch={10} windowSize={15} // Commenting the line below as it breaks the unread indicator test From d9dfaa734fcfa8151885810dac9c73872a83ab57 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Tue, 19 Sep 2023 11:29:48 +0200 Subject: [PATCH 063/151] sort prop types --- src/components/ReportActionItem/MoneyRequestView.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index 97eb23f80eeb..386d8090d7c4 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -33,6 +33,10 @@ import OfflineWithFeedback from '../OfflineWithFeedback'; import categoryPropTypes from '../categoryPropTypes'; const propTypes = { + /** Whether we should display the horizontal rule below the component */ + shouldShowHorizontalRule: PropTypes.bool.isRequired, + + /* Onyx Props */ /** List of betas available to current user */ betas: PropTypes.arrayOf(PropTypes.string), @@ -42,16 +46,12 @@ const propTypes = { /** The expense report or iou report (only will have a value if this is a transaction thread) */ parentReport: iouReportPropTypes, - /* Onyx Props */ /** Collection of categories attached to a policy */ policyCategories: PropTypes.objectOf(categoryPropTypes), /** The transaction associated with the transactionThread */ transaction: transactionPropTypes, - /** Whether we should display the horizontal rule below the component */ - shouldShowHorizontalRule: PropTypes.bool.isRequired, - ...withCurrentUserPersonalDetailsPropTypes, }; From 4f875ea1641b11fa676b5bafbd3c0f3facd2d1fc Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Tue, 19 Sep 2023 11:37:24 +0200 Subject: [PATCH 064/151] simplify helpers --- src/libs/OptionsListUtils.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 5e44babe1083..ff318d4fd1e5 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -605,9 +605,7 @@ function isCurrentUser(userDetails) { * @returns {Boolean} */ function getEnabledCategoriesCount(options) { - return _.chain(options) - .filter((option) => Boolean(option.enabled)) - .value().length; + return _.filter(options, (option) => option.enabled).length; } /** @@ -619,7 +617,7 @@ function getEnabledCategoriesCount(options) { * @returns {Boolean} */ function hasEnabledOptions(options) { - return _.some(options, (option) => Boolean(option.enabled)); + return _.some(options, (option) => option.enabled); } /** From d962ce18928ab774f8f50e26a77c97f5d9e3b96e Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Tue, 19 Sep 2023 11:37:41 +0200 Subject: [PATCH 065/151] create a handler --- src/pages/EditRequestCategoryPage.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/pages/EditRequestCategoryPage.js b/src/pages/EditRequestCategoryPage.js index ba94c2b40800..b1ee6f3384f6 100644 --- a/src/pages/EditRequestCategoryPage.js +++ b/src/pages/EditRequestCategoryPage.js @@ -20,6 +20,12 @@ const propTypes = { function EditRequestCategoryPage({defaultCategory, policyID, onSubmit}) { const {translate} = useLocalize(); + const selectCategory = (category) => { + onSubmit({ + category: category.searchText, + }); + }; + return ( - onSubmit({ - category: category.searchText, - }) - } + onSubmit={selectCategory} /> ); From 72bd7e84f032a9c053adfba33a58e9b30ba710ed Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Tue, 19 Sep 2023 11:46:59 +0200 Subject: [PATCH 066/151] reuse iuo props --- src/pages/iou/MoneyRequestCategoryPage.js | 9 ++++----- src/pages/iou/propTypes/index.js | 4 ++++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/pages/iou/MoneyRequestCategoryPage.js b/src/pages/iou/MoneyRequestCategoryPage.js index adba5755496d..92934a505a04 100644 --- a/src/pages/iou/MoneyRequestCategoryPage.js +++ b/src/pages/iou/MoneyRequestCategoryPage.js @@ -11,6 +11,7 @@ import CategoryPicker from '../../components/CategoryPicker'; import ONYXKEYS from '../../ONYXKEYS'; import reportPropTypes from '../reportPropTypes'; import * as IOU from '../../libs/actions/IOU'; +import {iouPropTypes, iouDefaultProps} from './propTypes'; const propTypes = { /** Navigation route context info provided by react navigation */ @@ -25,19 +26,17 @@ const propTypes = { }), }).isRequired, + /* Onyx Props */ /** The report currently being used */ report: reportPropTypes, - /* Onyx Props */ /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ - iou: PropTypes.shape({ - category: PropTypes.string.isRequired, - }), + iou: iouPropTypes, }; const defaultProps = { report: {}, - iou: {}, + iou: iouDefaultProps, }; function MoneyRequestCategoryPage({route, report, iou}) { diff --git a/src/pages/iou/propTypes/index.js b/src/pages/iou/propTypes/index.js index 5ecd00d11876..586f8424a2c2 100644 --- a/src/pages/iou/propTypes/index.js +++ b/src/pages/iou/propTypes/index.js @@ -18,6 +18,9 @@ const iouPropTypes = PropTypes.shape({ /** The merchant name */ merchant: PropTypes.string, + /** The category name */ + category: PropTypes.string, + /** The tag */ tag: PropTypes.string, @@ -37,6 +40,7 @@ const iouDefaultProps = { currency: CONST.CURRENCY.USD, comment: '', merchant: '', + category: '', tag: '', created: '', participants: [], From 37d67702d350aaa6dca47ae274e8e4e1529562eb Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Tue, 19 Sep 2023 11:57:36 +0200 Subject: [PATCH 067/151] clarify props --- src/components/ReportActionItem/MoneyRequestView.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index 386d8090d7c4..0aba850b2145 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -33,6 +33,9 @@ import OfflineWithFeedback from '../OfflineWithFeedback'; import categoryPropTypes from '../categoryPropTypes'; const propTypes = { + /** The report currently being looked at */ + report: reportPropTypes.isRequired, + /** Whether we should display the horizontal rule below the component */ shouldShowHorizontalRule: PropTypes.bool.isRequired, @@ -40,9 +43,6 @@ const propTypes = { /** List of betas available to current user */ betas: PropTypes.arrayOf(PropTypes.string), - /** The report currently being looked at */ - report: reportPropTypes.isRequired, - /** The expense report or iou report (only will have a value if this is a transaction thread) */ parentReport: iouReportPropTypes, From 3353869cc4725020a9205be609395b42ce7b0af9 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Tue, 19 Sep 2023 11:58:04 +0200 Subject: [PATCH 068/151] clarify return type --- src/libs/OptionsListUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index ff318d4fd1e5..6c17bad76dd5 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -602,7 +602,7 @@ function isCurrentUser(userDetails) { * @param {Object[]} options - an initial strings array * @param {Boolean} options[].enabled - a flag to enable/disable option in a list * @param {String} options[].name - a name of an option - * @returns {Boolean} + * @returns {Number} */ function getEnabledCategoriesCount(options) { return _.filter(options, (option) => option.enabled).length; From d903534925e7317cc03da0f989d87dc5bb24a090 Mon Sep 17 00:00:00 2001 From: jeet-dhandha Date: Tue, 19 Sep 2023 16:19:38 +0530 Subject: [PATCH 069/151] created platform based files --- src/components/MenuItem.js | 20 ++++------------ .../MenuItemRenderHTMLTitle/index.js | 23 +++++++++++++++++++ .../MenuItemRenderHTMLTitle/index.native.js | 17 ++++++++++++++ .../MenuItemRenderHTMLTitle/propTypes.js | 8 +++++++ 4 files changed, 53 insertions(+), 15 deletions(-) create mode 100644 src/components/MenuItemRenderHTMLTitle/index.js create mode 100644 src/components/MenuItemRenderHTMLTitle/index.native.js create mode 100644 src/components/MenuItemRenderHTMLTitle/propTypes.js diff --git a/src/components/MenuItem.js b/src/components/MenuItem.js index bb4eeb7a18ac..268351699567 100644 --- a/src/components/MenuItem.js +++ b/src/components/MenuItem.js @@ -24,11 +24,7 @@ import variables from '../styles/variables'; import * as Session from '../libs/actions/Session'; import Hoverable from './Hoverable'; import useWindowDimensions from '../hooks/useWindowDimensions'; -import RenderHTML from './RenderHTML'; -import getPlatform from '../libs/getPlatform'; - -const platform = getPlatform(); -const isNative = platform === CONST.PLATFORM.IOS || platform === CONST.PLATFORM.ANDROID; +import MenuItemRenderHTMLTitle from './MenuItemRenderHTMLTitle'; const propTypes = menuItemPropTypes; @@ -251,16 +247,10 @@ const MenuItem = React.forwardRef((props, ref) => { )} - {Boolean(props.title) && - (Boolean(props.shouldRenderAsHTML) || (Boolean(props.shouldParseTitle) && Boolean(html.length))) && - (isNative ? ( - - ) : ( - - - - ))} - {!props.shouldRenderAsHTML && !html.length && Boolean(props.title) && ( + {Boolean(props.title) && (Boolean(props.shouldRenderAsHTML) || (Boolean(props.shouldParseTitle) && Boolean(html.length))) && ( + + )} + {!props.shouldRenderAsHTML && !props.shouldParseTitle && Boolean(props.title) && ( + + + ); +} + +MenuItemRenderHTMLTitle.propTypes = propTypes; +MenuItemRenderHTMLTitle.defaultProps = defaultProps; +MenuItemRenderHTMLTitle.displayName = 'MenuItemRenderHTMLTitle'; + +export default MenuItemRenderHTMLTitle; diff --git a/src/components/MenuItemRenderHTMLTitle/index.native.js b/src/components/MenuItemRenderHTMLTitle/index.native.js new file mode 100644 index 000000000000..b3dff8d77eff --- /dev/null +++ b/src/components/MenuItemRenderHTMLTitle/index.native.js @@ -0,0 +1,17 @@ +import React from 'react'; +import RenderHTML from '../RenderHTML'; +import menuItemRenderHTMLTitlePropTypes from './propTypes'; + +const propTypes = menuItemRenderHTMLTitlePropTypes; + +const defaultProps = {}; + +function MenuItemRenderHTMLTitle(props) { + return ; +} + +MenuItemRenderHTMLTitle.propTypes = propTypes; +MenuItemRenderHTMLTitle.defaultProps = defaultProps; +MenuItemRenderHTMLTitle.displayName = 'MenuItemRenderHTMLTitle'; + +export default MenuItemRenderHTMLTitle; diff --git a/src/components/MenuItemRenderHTMLTitle/propTypes.js b/src/components/MenuItemRenderHTMLTitle/propTypes.js new file mode 100644 index 000000000000..68e279eb28c3 --- /dev/null +++ b/src/components/MenuItemRenderHTMLTitle/propTypes.js @@ -0,0 +1,8 @@ +import PropTypes from 'prop-types'; + +const propTypes = { + /** Processed title to display for the MenuItem */ + title: PropTypes.string.isRequired, +}; + +export default propTypes; From 1da55754141df37fbe0d311906a4711bfcad0fe7 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 19 Sep 2023 17:41:16 +0200 Subject: [PATCH 070/151] [TS migration] Migrate 'MoveToIndexedDB.js' lib to TypeScript --- src/libs/migrateOnyx.js | 2 - src/libs/migrations/MoveToIndexedDB.js | 75 -------------------------- tests/unit/MigrationTest.js | 68 ----------------------- 3 files changed, 145 deletions(-) delete mode 100644 src/libs/migrations/MoveToIndexedDB.js diff --git a/src/libs/migrateOnyx.js b/src/libs/migrateOnyx.js index 9389a9b66fbc..c1129817ef0a 100644 --- a/src/libs/migrateOnyx.js +++ b/src/libs/migrateOnyx.js @@ -3,7 +3,6 @@ import Log from './Log'; import AddEncryptedAuthToken from './migrations/AddEncryptedAuthToken'; import RenameActiveClientsKey from './migrations/RenameActiveClientsKey'; import RenamePriorityModeKey from './migrations/RenamePriorityModeKey'; -import MoveToIndexedDB from './migrations/MoveToIndexedDB'; import RenameExpensifyNewsStatus from './migrations/RenameExpensifyNewsStatus'; import AddLastVisibleActionCreated from './migrations/AddLastVisibleActionCreated'; import KeyReportActionsByReportActionID from './migrations/KeyReportActionsByReportActionID'; @@ -16,7 +15,6 @@ export default function () { return new Promise((resolve) => { // Add all migrations to an array so they are executed in order const migrationPromises = [ - MoveToIndexedDB, RenameActiveClientsKey, RenamePriorityModeKey, AddEncryptedAuthToken, diff --git a/src/libs/migrations/MoveToIndexedDB.js b/src/libs/migrations/MoveToIndexedDB.js deleted file mode 100644 index 1f62985ed7bf..000000000000 --- a/src/libs/migrations/MoveToIndexedDB.js +++ /dev/null @@ -1,75 +0,0 @@ -import _ from 'underscore'; -import Onyx from 'react-native-onyx'; - -import Log from '../Log'; -import ONYXKEYS from '../../ONYXKEYS'; -import getPlatform from '../getPlatform'; -import CONST from '../../CONST'; - -/** - * Test whether the current platform is eligible for migration - * This migration is only applicable for desktop/web - * We're also skipping logged-out users as there would be nothing to migrate - * - * @returns {Boolean} - */ -function shouldMigrate() { - const isTargetPlatform = _.contains([CONST.PLATFORM.WEB, CONST.PLATFORM.DESKTOP], getPlatform()); - if (!isTargetPlatform) { - Log.info('[Migrate Onyx] Skipped migration MoveToIndexedDB (Not applicable to current platform)'); - return false; - } - - const session = window.localStorage.getItem(ONYXKEYS.SESSION); - if (!session || !session.includes('authToken')) { - Log.info('[Migrate Onyx] Skipped migration MoveToIndexedDB (Not applicable to logged out users)'); - return false; - } - - return true; -} - -/** - * Find Onyx data and move it from local storage to IndexedDB - * - * @returns {Promise} - */ -function applyMigration() { - const onyxKeys = new Set(_.values(ONYXKEYS)); - const onyxCollections = _.values(ONYXKEYS.COLLECTION); - - // Prepare a key-value dictionary of keys eligible for migration - // Targeting existing Onyx keys in local storage or any key prefixed by a collection name - const dataToMigrate = _.chain(window.localStorage) - .keys() - .filter((key) => onyxKeys.has(key) || _.some(onyxCollections, (collectionKey) => key.startsWith(collectionKey))) - .map((key) => [key, JSON.parse(window.localStorage.getItem(key))]) - .object() - .value(); - - // Move the data in Onyx and only then delete it from local storage - return Onyx.multiSet(dataToMigrate).then(() => _.each(dataToMigrate, (value, key) => window.localStorage.removeItem(key))); -} - -/** - * Migrate Web/Desktop storage to IndexedDB - * - * @returns {Promise} - */ -export default function () { - if (!shouldMigrate()) { - return Promise.resolve(); - } - - return new Promise((resolve, reject) => { - applyMigration() - .then(() => { - Log.info('[Migrate Onyx] Ran migration MoveToIndexedDB'); - resolve(); - }) - .catch((e) => { - Log.alert('[Migrate Onyx] MoveToIndexedDB failed', {error: e.message, stack: e.stack}, false); - reject(e); - }); - }); -} diff --git a/tests/unit/MigrationTest.js b/tests/unit/MigrationTest.js index 0171ee640226..71f7d57fce36 100644 --- a/tests/unit/MigrationTest.js +++ b/tests/unit/MigrationTest.js @@ -1,11 +1,8 @@ import Onyx from 'react-native-onyx'; import _ from 'underscore'; import waitForPromisesToResolve from '../utils/waitForPromisesToResolve'; -import CONST from '../../src/CONST'; import Log from '../../src/libs/Log'; -import getPlatform from '../../src/libs/getPlatform'; import AddLastVisibleActionCreated from '../../src/libs/migrations/AddLastVisibleActionCreated'; -import MoveToIndexedDB from '../../src/libs/migrations/MoveToIndexedDB'; import KeyReportActionsByReportActionID from '../../src/libs/migrations/KeyReportActionsByReportActionID'; import PersonalDetailsByAccountID from '../../src/libs/migrations/PersonalDetailsByAccountID'; import CheckForPreviousReportActionID from '../../src/libs/migrations/CheckForPreviousReportActionID'; @@ -29,71 +26,6 @@ describe('Migrations', () => { return waitForPromisesToResolve(); }); - describe('MoveToIndexedDb', () => { - let mockMultiSet; - beforeEach(() => { - getPlatform.mockImplementation(() => CONST.PLATFORM.WEB); - mockMultiSet = jest.spyOn(Onyx, 'multiSet').mockImplementation(() => Promise.resolve()); - localStorage.clear(); - }); - - afterAll(() => { - mockMultiSet.mockRestore(Onyx, 'multiSet'); - localStorage.clear(); - }); - - it('Should do nothing for non web/desktop platforms', () => { - // Given the migration is not running on web or desktop - getPlatform.mockImplementation(() => CONST.PLATFORM.ANDROID); - - // When the migration runs - return MoveToIndexedDB().then(() => { - // Then we don't expect any storage calls - expect(Onyx.multiSet).not.toHaveBeenCalled(); - }); - }); - - it('Should do nothing when there is no old session data', () => { - // Given no session in old storage medium (localStorage) - localStorage.removeItem(ONYXKEYS.SESSION); - - // When the migration runs - return MoveToIndexedDB().then(() => { - // Then we don't expect any storage calls - expect(Onyx.multiSet).not.toHaveBeenCalled(); - }); - }); - - it('Should migrate Onyx keys in localStorage to (new) Onyx', () => { - // Given some old data exists in storage - const data = { - [ONYXKEYS.SESSION]: {authToken: 'mock-token', loading: false}, - [ONYXKEYS.ACCOUNT]: {email: 'test@mock.com'}, - [ONYXKEYS.NETWORK]: {isOffline: true}, - }; - - _.forEach(data, (value, key) => localStorage.setItem(key, JSON.stringify(value))); - - // When the migration runs - return MoveToIndexedDB().then(() => { - // Then multiset should be called with all the data available in localStorage - expect(Onyx.multiSet).toHaveBeenCalledWith(data); - }); - }); - - it('Should not clear non Onyx keys from localStorage', () => { - // Given some Onyx and non-Onyx data exists in localStorage - localStorage.setItem(ONYXKEYS.SESSION, JSON.stringify({authToken: 'mock-token'})); - localStorage.setItem('non-onyx-item', 'MOCK'); - - // When the migration runs - return MoveToIndexedDB().then(() => { - // Then non-Onyx data should remain in localStorage - expect(localStorage.getItem('non-onyx-item')).toEqual('MOCK'); - }); - }); - }); - describe('AddLastVisibleActionCreated', () => { it('Should add lastVisibleActionCreated wherever lastActionCreated currently is', () => Onyx.multiSet({ From 46764751277a61d9f145785fe06479c2b83d0848 Mon Sep 17 00:00:00 2001 From: Ana Margarida Silva Date: Tue, 19 Sep 2023 16:54:36 +0100 Subject: [PATCH 071/151] feat: support missing report when requesting money from FAB This was once implemented by @rezkiy37 but it was mistakenly in another PR --- .../MoneyRequestParticipantsPage.js | 18 +++++++++++++++--- .../MoneyRequestParticipantsSelector.js | 2 +- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js index 1d9f12a9cdbb..f0ec28336f5a 100644 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js @@ -62,7 +62,19 @@ function MoneyRequestParticipantsPage({iou, selectedTab, route}) { setHeaderTitle(_.isEmpty(iou.participants) ? translate('tabSelector.manual') : translate('iou.split')); }, [iou.participants, isDistanceRequest, translate]); - const navigateToNextStep = (moneyRequestType) => { + const navigateToRequestStep = (moneyRequestType, option) => { + if (option.reportID) { + isNewReportIDSelectedLocally.current = true; + IOU.setMoneyRequestId(`${moneyRequestType}${option.reportID}`); + Navigation.navigate(ROUTES.getMoneyRequestConfirmationRoute(moneyRequestType, option.reportID)); + return; + } + + IOU.setMoneyRequestId(moneyRequestType); + Navigation.navigate(ROUTES.getMoneyRequestConfirmationRoute(moneyRequestType, reportID.current)); + }; + + const navigateToSplitStep = (moneyRequestType) => { IOU.setMoneyRequestId(moneyRequestType); Navigation.navigate(ROUTES.getMoneyRequestConfirmationRoute(moneyRequestType, reportID.current)); }; @@ -112,8 +124,8 @@ function MoneyRequestParticipantsPage({iou, selectedTab, route}) { ref={(el) => (optionsSelectorRef.current = el)} participants={iou.participants} onAddParticipants={IOU.setMoneyRequestParticipants} - navigateToRequest={() => navigateToNextStep(CONST.IOU.MONEY_REQUEST_TYPE.REQUEST)} - navigateToSplit={() => navigateToNextStep(CONST.IOU.MONEY_REQUEST_TYPE.SPLIT)} + navigateToRequest={(option) => navigateToRequestStep(CONST.IOU.MONEY_REQUEST_TYPE.REQUEST, option)} + navigateToSplit={() => navigateToSplitStep(CONST.IOU.MONEY_REQUEST_TYPE.SPLIT)} safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle} iouType={iouType.current} isDistanceRequest={isDistanceRequest} diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js index 9ff787ebe21b..cf69e6477d3a 100755 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js @@ -150,7 +150,7 @@ function MoneyRequestParticipantsSelector({ */ const addSingleParticipant = (option) => { onAddParticipants([{accountID: option.accountID, login: option.login, isPolicyExpenseChat: option.isPolicyExpenseChat, reportID: option.reportID, selected: true}]); - navigateToRequest(); + navigateToRequest(option); }; /** From 83e24ec882a668ee71b6664a2354c301dd9bc108 Mon Sep 17 00:00:00 2001 From: jeet-dhandha Date: Tue, 19 Sep 2023 21:59:53 +0530 Subject: [PATCH 072/151] Fixed Overflow --- src/components/MenuItem.js | 6 ++++-- src/styles/styles.js | 4 ++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/MenuItem.js b/src/components/MenuItem.js index 268351699567..824b23c84898 100644 --- a/src/components/MenuItem.js +++ b/src/components/MenuItem.js @@ -1,6 +1,6 @@ import _ from 'underscore'; import React, {useEffect, useMemo} from 'react'; -import {View} from 'react-native'; +import {ScrollView, View} from 'react-native'; import ExpensiMark from 'expensify-common/lib/ExpensiMark'; import Text from './Text'; import styles from '../styles/styles'; @@ -248,7 +248,9 @@ const MenuItem = React.forwardRef((props, ref) => { )} {Boolean(props.title) && (Boolean(props.shouldRenderAsHTML) || (Boolean(props.shouldParseTitle) && Boolean(html.length))) && ( - + + + )} {!props.shouldRenderAsHTML && !props.shouldParseTitle && Boolean(props.title) && ( ({ height: 30, width: '100%', }, + + menuItemHtmlRendererScrollView: { + maxHeight: 115, + }, }); // For now we need to export the styles function that takes the theme as an argument From 0ad4fbaaea7f80d38914b8ab0c3f4f8e4a31faff Mon Sep 17 00:00:00 2001 From: jeet-dhandha Date: Tue, 19 Sep 2023 22:12:38 +0530 Subject: [PATCH 073/151] Fixed app not scrolling --- src/components/MenuItem.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/MenuItem.js b/src/components/MenuItem.js index 824b23c84898..c435d6330c15 100644 --- a/src/components/MenuItem.js +++ b/src/components/MenuItem.js @@ -25,6 +25,7 @@ import * as Session from '../libs/actions/Session'; import Hoverable from './Hoverable'; import useWindowDimensions from '../hooks/useWindowDimensions'; import MenuItemRenderHTMLTitle from './MenuItemRenderHTMLTitle'; +import {PressableWithoutFeedback} from './Pressable'; const propTypes = menuItemPropTypes; @@ -249,7 +250,9 @@ const MenuItem = React.forwardRef((props, ref) => { {Boolean(props.title) && (Boolean(props.shouldRenderAsHTML) || (Boolean(props.shouldParseTitle) && Boolean(html.length))) && ( - + + + )} {!props.shouldRenderAsHTML && !props.shouldParseTitle && Boolean(props.title) && ( From 120eb74916bdd35ce88225d463dd27cff773ede9 Mon Sep 17 00:00:00 2001 From: jeet-dhandha Date: Tue, 19 Sep 2023 22:23:38 +0530 Subject: [PATCH 074/151] fixed eslint --- src/components/MenuItem.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/MenuItem.js b/src/components/MenuItem.js index c435d6330c15..12dbbf1e4903 100644 --- a/src/components/MenuItem.js +++ b/src/components/MenuItem.js @@ -25,7 +25,7 @@ import * as Session from '../libs/actions/Session'; import Hoverable from './Hoverable'; import useWindowDimensions from '../hooks/useWindowDimensions'; import MenuItemRenderHTMLTitle from './MenuItemRenderHTMLTitle'; -import {PressableWithoutFeedback} from './Pressable'; +import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback'; const propTypes = menuItemPropTypes; @@ -250,7 +250,7 @@ const MenuItem = React.forwardRef((props, ref) => { {Boolean(props.title) && (Boolean(props.shouldRenderAsHTML) || (Boolean(props.shouldParseTitle) && Boolean(html.length))) && ( - + From b3d2a0cc67d43ac6f35de668d4e1e4bf10fe27eb Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Wed, 20 Sep 2023 10:47:25 +0700 Subject: [PATCH 075/151] resolve leftover conflict --- src/pages/home/ReportScreen.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index a8438e0a4f09..794471864aa4 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -352,7 +352,7 @@ function ReportScreen({ // eslint-disable-next-line rulesdir/no-negated-variables const shouldShowNotFoundPage = useMemo( - () => (!_.isEmpty(report) && !isDefaultReport && !report.reportID && !isOptimisticDelete && !report.isLoadingReportActions && !isLoading) || shouldHideReport, + () => (!_.isEmpty(report) && !isDefaultReport && !report.reportID && !isOptimisticDelete && !report.isLoadingReportActions && !isLoading && !userLeavingStatus) || shouldHideReport, [report, isLoading, shouldHideReport, isDefaultReport, isOptimisticDelete], ); @@ -368,11 +368,7 @@ function ReportScreen({ shouldEnableKeyboardAvoidingView={isTopMostReportId} > >>>>>> d0b49122d07ed51b974acce3981883e9b049a30c subtitleKey="notFound.noAccess" shouldShowCloseButton={false} shouldShowBackButton={isSmallScreenWidth} From 91c533e48386812078e37618530e08237a5be9aa Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Wed, 20 Sep 2023 10:48:06 +0700 Subject: [PATCH 076/151] resolve leftover conflict --- src/pages/home/ReportScreen.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 794471864aa4..213f004ac416 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -353,7 +353,7 @@ function ReportScreen({ // eslint-disable-next-line rulesdir/no-negated-variables const shouldShowNotFoundPage = useMemo( () => (!_.isEmpty(report) && !isDefaultReport && !report.reportID && !isOptimisticDelete && !report.isLoadingReportActions && !isLoading && !userLeavingStatus) || shouldHideReport, - [report, isLoading, shouldHideReport, isDefaultReport, isOptimisticDelete], + [report, isLoading, shouldHideReport, isDefaultReport, isOptimisticDelete, userLeavingStatus], ); return ( From 89263770bad930885e016f1ce3d1449d30174656 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Wed, 20 Sep 2023 10:50:17 +0700 Subject: [PATCH 077/151] remove unnecessary code --- src/pages/home/ReportScreen.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 213f004ac416..6c11dcc01983 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -290,10 +290,6 @@ function ReportScreen({ fetchReportIfNeeded(); ComposerActions.setShouldShowComposeInput(true); return () => { - if (!unsubscribeVisibilityListener) { - return; - } - unsubscribeVisibilityListener(); if (didSubscribeToReportLeavingEvents) { Report.unsubscribeFromLeavingRoomReportChannel(report.reportID); } From b2303b253db992b108842ae273f770592b2d2a7d Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Wed, 20 Sep 2023 10:55:54 +0700 Subject: [PATCH 078/151] change didSubscribeToReportLeavingEvents check to early return --- src/pages/home/ReportScreen.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 6c11dcc01983..8a9f50518dc8 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -290,9 +290,11 @@ function ReportScreen({ fetchReportIfNeeded(); ComposerActions.setShouldShowComposeInput(true); return () => { - if (didSubscribeToReportLeavingEvents) { - Report.unsubscribeFromLeavingRoomReportChannel(report.reportID); + if (!didSubscribeToReportLeavingEvents) { + return; } + + Report.unsubscribeFromLeavingRoomReportChannel(report.reportID); }; // I'm disabling the warning, as it expects to use exhaustive deps, even though we want this useEffect to run only on the first render. From 00ddd452ee93d0df71c7510df540a873a73e2baa Mon Sep 17 00:00:00 2001 From: Yonathan Evan Christy Date: Wed, 20 Sep 2023 15:05:54 +0700 Subject: [PATCH 079/151] Include newest version of eslint-config-expensify = 2.0.39 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 40e2ca717931..8e0b493b495e 100644 --- a/package.json +++ b/package.json @@ -232,7 +232,7 @@ "electron-builder": "24.6.4", "eslint": "^7.6.0", "eslint-config-airbnb-typescript": "^17.1.0", - "eslint-config-expensify": "^2.0.38", + "eslint-config-expensify": "^2.0.39", "eslint-config-prettier": "^8.8.0", "eslint-plugin-jest": "^24.1.0", "eslint-plugin-jsdoc": "^46.2.6", From abd2d25dbdd9cf3e699a6d0d944e06e24dfc30f3 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 20 Sep 2023 11:43:50 +0200 Subject: [PATCH 080/151] re-test From b0dd5f61fe3114917bd2aa0a125d16e6eebad044 Mon Sep 17 00:00:00 2001 From: Yonathan Evan Christy Date: Wed, 20 Sep 2023 16:58:17 +0700 Subject: [PATCH 081/151] Add `// eslint-disable-next-line` for Affected files --- .eslintrc.js | 1 + src/components/LHNOptionsList/OptionRowLHNData.js | 2 ++ src/pages/EditRequestPage.js | 1 + src/pages/iou/MoneyRequestTagPage.js | 2 ++ src/pages/iou/steps/MoneyRequestConfirmPage.js | 2 ++ 5 files changed, 8 insertions(+) diff --git a/.eslintrc.js b/.eslintrc.js index ac4546567833..79239a32395c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -39,6 +39,7 @@ module.exports = { files: ['*.js', '*.jsx', '*.ts', '*.tsx'], rules: { 'rulesdir/onyx-props-must-have-default': 'off', + 'rulesdir/no-multiple-onyx-in-file': 'error', 'react-native-a11y/has-accessibility-hint': ['off'], 'react-native-a11y/has-valid-accessibility-descriptors': [ 'error', diff --git a/src/components/LHNOptionsList/OptionRowLHNData.js b/src/components/LHNOptionsList/OptionRowLHNData.js index 2c51d6332946..37bc4df9ea3a 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.js +++ b/src/components/LHNOptionsList/OptionRowLHNData.js @@ -195,6 +195,7 @@ export default React.memo( key: ONYXKEYS.NVP_PREFERRED_LOCALE, }, }), + // eslint-disable-next-line withOnyx({ parentReportActions: { key: ({fullReport}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${fullReport.parentReportID}`, @@ -209,6 +210,7 @@ export default React.memo( // However, performance overhead of this is minimized by using memos inside the component. receiptTransactions: {key: ONYXKEYS.COLLECTION.TRANSACTION}, }), + // eslint-disable-next-line withOnyx({ transaction: { key: ({fullReport, parentReportActions}) => diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index 2123f1bf10da..466c5e1a1df3 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -194,6 +194,7 @@ export default compose( key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.threadReportID}`, }, }), + // eslint-disable-next-line withOnyx({ parentReport: { key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT}${report ? report.parentReportID : '0'}`, diff --git a/src/pages/iou/MoneyRequestTagPage.js b/src/pages/iou/MoneyRequestTagPage.js index a1795d50df8a..5247dbd3f672 100644 --- a/src/pages/iou/MoneyRequestTagPage.js +++ b/src/pages/iou/MoneyRequestTagPage.js @@ -91,12 +91,14 @@ export default compose( key: ONYXKEYS.IOU, }, }), + // eslint-disable-next-line withOnyx({ report: { // Fetch report ID from IOU participants if no report ID is set in route key: ({route, iou}) => `${ONYXKEYS.COLLECTION.REPORT}${lodashGet(route, 'params.reportID', '') || lodashGet(iou, 'participants.0.reportID', '')}`, }, }), + // eslint-disable-next-line withOnyx({ policyTags: { key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report ? report.policyID : '0'}`, diff --git a/src/pages/iou/steps/MoneyRequestConfirmPage.js b/src/pages/iou/steps/MoneyRequestConfirmPage.js index 224f661915d8..cc37ee6de818 100644 --- a/src/pages/iou/steps/MoneyRequestConfirmPage.js +++ b/src/pages/iou/steps/MoneyRequestConfirmPage.js @@ -350,6 +350,7 @@ export default compose( key: ONYXKEYS.IOU, }, }), + // eslint-disable-next-line withOnyx({ report: { key: ({route, iou}) => { @@ -369,6 +370,7 @@ export default compose( key: `${ONYXKEYS.COLLECTION.SELECTED_TAB}${CONST.TAB.RECEIPT_TAB_ID}`, }, }), + // eslint-disable-next-line withOnyx({ policy: { key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`, From 5b2e89ce4520a9967a009d5e72ee0f951680a45a Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 20 Sep 2023 15:49:36 +0530 Subject: [PATCH 082/151] delete existing routes --- docs/_data/_routes.yml | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/docs/_data/_routes.yml b/docs/_data/_routes.yml index 32c8a5211ee2..85593156a4b1 100644 --- a/docs/_data/_routes.yml +++ b/docs/_data/_routes.yml @@ -2,25 +2,3 @@ home: href: home title: Welcome to ExpensifyHelp! description: Find the answers to all of your questions about receipts, expenses, corporate cards, or anything else in the spend management universe. - -# Hubs are comprised of sections and articles. Sections contain multiple related articles, but there can be standalone articles as well -hubs: - - href: split-bills - title: Split bills - description: With only a couple of clicks, split bills with your friends or coworkers. - icon: /assets/images/paper-airplane.svg - - - href: request-money - title: Request money - icon: /assets/images/money-case.svg - description: Request money for work expenses, bills, or a night out with friends. - - - href: playbooks - title: Playbooks - icon: /assets/images/playbook.svg - description: Best practices for how to best deploy Expensify for your business - - - href: other - title: Other - description: Everything else you're looking for is right here. - icon: /assets/images/lightbulb.svg From 3ef926c395c204779c891d80c77b2d84d1a0903a Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 20 Sep 2023 15:49:49 +0530 Subject: [PATCH 083/151] delete existing hubs --- docs/hubs/other.html | 6 ------ docs/hubs/playbooks.html | 6 ------ docs/hubs/request-money.html | 6 ------ docs/hubs/split-bills.html | 6 ------ 4 files changed, 24 deletions(-) delete mode 100644 docs/hubs/other.html delete mode 100644 docs/hubs/playbooks.html delete mode 100644 docs/hubs/request-money.html delete mode 100644 docs/hubs/split-bills.html diff --git a/docs/hubs/other.html b/docs/hubs/other.html deleted file mode 100644 index 7d6d7f204062..000000000000 --- a/docs/hubs/other.html +++ /dev/null @@ -1,6 +0,0 @@ ---- -layout: default -title: Other ---- - -{% include hub.html %} diff --git a/docs/hubs/playbooks.html b/docs/hubs/playbooks.html deleted file mode 100644 index 0f15922fd061..000000000000 --- a/docs/hubs/playbooks.html +++ /dev/null @@ -1,6 +0,0 @@ ---- -layout: default -title: Playbooks ---- - -{% include hub.html %} diff --git a/docs/hubs/request-money.html b/docs/hubs/request-money.html deleted file mode 100644 index e3ef68f5c050..000000000000 --- a/docs/hubs/request-money.html +++ /dev/null @@ -1,6 +0,0 @@ ---- -layout: default -title: Request money ---- - -{% include hub.html %} diff --git a/docs/hubs/split-bills.html b/docs/hubs/split-bills.html deleted file mode 100644 index 6464ab62ef07..000000000000 --- a/docs/hubs/split-bills.html +++ /dev/null @@ -1,6 +0,0 @@ ---- -layout: default -title: Split bills ---- - -{% include hub.html %} From 08ebea74c228e5c6c6bb093847ce8ab5897ec5d6 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 20 Sep 2023 15:51:05 +0530 Subject: [PATCH 084/151] delete existing articles --- ...e-Share-for-ExpensifyApproved!-Partners.md | 16 --- .../other/Enable-Location-Access-on-Web.md | 55 ---------- docs/articles/other/Everything-About-Chat.md | 87 --------------- .../other/Expensify-Chat-For-Admins.md | 26 ----- ...Expensify-Chat-For-Conference-Attendees.md | 35 ------ .../Expensify-Chat-For-Conference-Speakers.md | 39 ------- docs/articles/other/Expensify-Lounge.md | 66 ------------ docs/articles/other/Insights.md | 100 ------------------ docs/articles/other/Referral-Program.md | 53 ---------- .../other/Your-Expensify-Account-Manager.md | 36 ------- .../other/Your-Expensify-Partner-Manager.md | 34 ------ 11 files changed, 547 deletions(-) delete mode 100644 docs/articles/other/Card-Revenue-Share-for-ExpensifyApproved!-Partners.md delete mode 100644 docs/articles/other/Enable-Location-Access-on-Web.md delete mode 100644 docs/articles/other/Everything-About-Chat.md delete mode 100644 docs/articles/other/Expensify-Chat-For-Admins.md delete mode 100644 docs/articles/other/Expensify-Chat-For-Conference-Attendees.md delete mode 100644 docs/articles/other/Expensify-Chat-For-Conference-Speakers.md delete mode 100644 docs/articles/other/Expensify-Lounge.md delete mode 100644 docs/articles/other/Insights.md delete mode 100644 docs/articles/other/Referral-Program.md delete mode 100644 docs/articles/other/Your-Expensify-Account-Manager.md delete mode 100644 docs/articles/other/Your-Expensify-Partner-Manager.md diff --git a/docs/articles/other/Card-Revenue-Share-for-ExpensifyApproved!-Partners.md b/docs/articles/other/Card-Revenue-Share-for-ExpensifyApproved!-Partners.md deleted file mode 100644 index 44614d506d49..000000000000 --- a/docs/articles/other/Card-Revenue-Share-for-ExpensifyApproved!-Partners.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -title: Expensify Card revenue share for ExpensifyApproved! partners -description: Earn money when your clients adopt the Expensify Card ---- - - -Start making more with us! We're thrilled to announce a new incentive for our US-based ExpensifyApproved! partner accountants. You can now earn additional income for your firm every time your client uses their Expensify Card. **In short, your firm gets 0.5% of your clients’ total Expensify Card spend as cash back**. The more your clients spend, the more cashback your firm receives!
-
This program is currently only available to US-based ExpensifyApproved! partner accountants. - -# How-to -To benefit from this program, all you need to do is ensure that you are listed as a domain admin on your client's Expensify account. If you're not currently a domain admin, your client can follow the instructions outlined in [our help article](https://community.expensify.com/discussion/5749/how-to-add-and-remove-domain-admins#:~:text=Domain%20Admins%20have%20total%20control,a%20member%20of%20the%20domain.) to assign you this role. -# FAQ -- What if my firm is not permitted to accept revenue share from our clients?
-
We understand that different firms may have different policies. If your firm is unable to accept this revenue share, you can pass the revenue share back to your client to give them an additional 0.5% of cash back using your own internal payment tools.

-- What if my firm does not wish to participate in the program?
-
Please reach out to your assigned partner manager at new.expensify.com to inform them you would not like to accept the revenue share nor do you want to pass the revenue share to your clients. diff --git a/docs/articles/other/Enable-Location-Access-on-Web.md b/docs/articles/other/Enable-Location-Access-on-Web.md deleted file mode 100644 index 6cc0d19e4cde..000000000000 --- a/docs/articles/other/Enable-Location-Access-on-Web.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -title: Enable Location Access on Web -description: How to enable location access for Expensify websites on your browser ---- - - -# About - -If you'd like to use features that rely on your current location you will need to enable location permissions for Expensify. You can find instructions for how to enable location settings on the three most common web browsers below. If your browser is not in the list then please do a web search for your browser and "enable location settings". - -# How-to - - -### Chrome -1. Open Chrome -2. At the top right, click the three-dot Menu > Settings -3. Click "Privacy and Security" and then "Site Settings" -4. Click Location -5. Check the "Not allowed to see your location" list to make sure expensify.com and new.expensify.com are not listed. If they are, click the delete icon next to them to allow location access - -[Chrome help page](https://support.google.com/chrome/answer/142065) - -### Firefox - -1. Open Firefox -2. In the URL bar enter "about:preferences" -3. On the left hand side select "Privacy & Security" -4. Scroll down to Permissions -5. Click on Settings next to Location -6. If location access is blocked for expensify.com or new.expensify.com, you can update it here to allow access - -[Firefox help page](https://support.mozilla.org/en-US/kb/permissions-manager-give-ability-store-passwords-set-cookies-more) - -### Safari -1. In the top menu bar click Safari -2. Then select Settings > Websites -3. Click Location on the left hand side -4. If expensify.com or new.expensify.com have "Deny" set as their access, update it to "Ask" or "Allow" - -Ask: The site must ask if it can use your location. -Deny: The site can’t use your location. -Allow: The site can always use your location. - -[Safari help page](https://support.apple.com/guide/safari/websites-ibrwe2159f50/mac) diff --git a/docs/articles/other/Everything-About-Chat.md b/docs/articles/other/Everything-About-Chat.md deleted file mode 100644 index d52932daa5ff..000000000000 --- a/docs/articles/other/Everything-About-Chat.md +++ /dev/null @@ -1,87 +0,0 @@ ---- -title: Everything About Chat -description: Everything you need to know about Expensify's Chat Features! ---- - - - -# What is Expensify Chat? -Expensify Chat is an ideal way to collaborate on expenses or payment requests by communicating in real-time with your accountant, clients, employees, or, friends. - -With Expensify Chat, you can start a conversation about that missing receipt your employee forgot to submit or chat about splitting that electric bill with your roommates. Through eChat, you can even request money from your friends after a night out on the town! - -# How to use Chat in Expensify -Download NewExpensify from the [App Store](https://apps.apple.com/us/app/expensify-cash/id1530278510) or [Google Play](https://play.google.com/store/apps/details?id=com.expensify.chat) to use the chat function. You can also access your account at new.expensify.com from your favorite web browser. - -After downloading the app, log into your new.expensify.com account (you’ll use the same login information as your Expensify Classic account). From there, you can customize your profile and start chatting immediately. - - -## Start Chatting -Select **New Chat** to chat one-on-one or **New Group** to start a group chat. -## Workspace Chat Rooms -In addition to 1:1 and group chat, members of a Workspace or Policy will have access to two additional rooms; the #announce and #admins rooms. -All workspace members are added to the #announce room by default. The #announce room lets you share important company announcements and have conversations between workspace members. - -All workspace admins can access the #admins room. Use the #admins room to collaborate with the other admins on your policy, and chat with your dedicated Expensify Onboarding Guide. If you have a subscription of 10 or more users, you're automatically assigned an Account Manager. You can ask for help and collaborate with your Account Manager in this same #admins room. Anytime someone on your team, your dedicated setup specialist, or your dedicated account manager makes any changes to your Workspace settings, that update is logged in the #admins room. -## How to format text - -- To italicize your message, place an underscore on both sides of the text: *text* -- To bold your message, place an asterisk on both sides of the text: **text** -- To strikethrough your message, place a tilde on both sides of the text: ~~text~~ -- To turn your message into code, place a backtick on both sides of the text: `text` -- To turn your text into a blockquote, add an angled bracket (>) in front of the text: - >your text -- To turn your message into a heading, place a number sign (#) in front of the text: -# Heading -- To turn your entire message into code block, place three backticks on both sides of the text: -``` -here's some text -and even more text -``` - -# FAQs -## How do I add more than one person to a chat? -Start by clicking the green chat **+** button and select **New Group**. Search for the people you want to invite and check the circle to the far right. Once you’ve selected everyone you want in the group chat, click the **Create Group** button at the bottom of your screen. - -## Can I add people to an existing Group chat? -Adding people to an existing group chat isn’t possible right now, so you’ll want to make a new group chat instead. - -## Someone I don’t recognize is in my #admins room for my workspace; who is it? -After creating your workspace, you’ll have a dedicated Expensify specialist who will help you onboard and answer your questions. You can chat with them directly in the #admins room or request a call to talk to them over the phone. Later, once you've finished onboarding, if you have a subscription of 10 or more users, a dedicated Account Manager is added to your #admins room for ongoing product support. - -## Can I force a chat to stay at the top of the chats list? -You sure can! Click on the chat you want to keep at the top of the list, and then click the small **pin** icon. If you want to unpin a chat, just click the **pin** icon again. - -# Deep Dive -## Chat display, aka Priority Mode -The way your chats display in the left-hand menu is customizable. We offer two different options; Most Recent mode and _#focus_ mode. - -- Most Recent mode will display all chats by default, sort them by the most recent, and keep your pinned chats at the top of the list. -- #focus mode will display only unread and pinned chats, and will sort them alphabetically. This setting is perfect for when you need to cut distractions and focus on a crucial project. - -You can find your display mode by clicking on your User Icon > Preferences > Priority Mode. - -## Inviting someone to Expensify Chat -If the person you want to chat with doesn’t appear in your contact list, simply type their email or phone number to invite them to chat! From there, they will receive an email with instructions and a link to create an account. - -Once they click the link, a new.expensify.com account is set up for them automatically (if they don't have one already), and they can start chatting with you immediately! - -## Flagging content as offensive -In order to maintain a safe community for our users, Expensify provides tools to report offensive content and unwanted behavior in Expensify Chat. If you see a message (or attachment/image) from another user that you’d like our moderators to review, you can flag it by clicking the flag icon in the message context menu (on desktop) or holding down on the message and selecting “Flag as offensive” (on mobile). - -![Moderation Context Menu](https://help.expensify.com/assets/images/moderation-context-menu.png){:width="100%"} - -Once the flag is selected, you will be asked to categorize the message (such as spam, bullying, and harassment). Select what you feel best represents the issue is with the content, and you’re done - the message will be sent off to our internal team for review. - -![Moderation Flagging Options](https://help.expensify.com/assets/images/moderation-flag-page.png){:width="100%"} - -Depending on the severity of the offense, messages can be hidden (with an option to reveal) or fully removed, and in extreme cases, the sender of the message can be temporarily or permanently blocked from posting. - -You will receive a whisper from Concierge any time your content has been flagged, as well as when you have successfully flagged a piece of content. - -![Moderation Reportee Whisper](https://help.expensify.com/assets/images/moderation-reportee-whisper.png){:width="100%"} -![Moderation Reporter Whisper](https://help.expensify.com/assets/images/moderation-reporter-whisper.png){:width="100%"} - -*Note: Any message sent in public chat rooms are automatically reviewed by an automated system looking for offensive content and sent to our moderators for final decisions if it is found.* - - diff --git a/docs/articles/other/Expensify-Chat-For-Admins.md b/docs/articles/other/Expensify-Chat-For-Admins.md deleted file mode 100644 index 247b2b0e03d0..000000000000 --- a/docs/articles/other/Expensify-Chat-For-Admins.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: Expensify Chat for Admins -description: Best Practices for Admins settings up Expensify Chat ---- - -## Overview -Expensify Chat is an incredible way to build a community and foster long-term relationships between event producers and attendees, or attendees with each other. Admins are a huge factor in the success of the connections built in Expensify Chat during the events, as they are generally the drivers of the conference schedule, and help ensure safety and respect is upheld by all attendees both on and offline. - -## Getting Started -We’ve rounded up some resources to get you set up on Expensify Chat and ready to start connecting with your session attendees: -- [How to get set up and start using Expensify Chat](https://help.expensify.com/articles/other/Everything-About-Chat#how-to-use-chat-in-expensify) -- [How to format text in Expensify Chat](https://help.expensify.com/articles/other/Everything-About-Chat#how-to-format-text) -- [How to flag content and/or users for moderation](https://help.expensify.com/articles/other/Everything-About-Chat#flagging-content-as-offensive) - -## Admin Best Practices -In order to get the most out of Expensify Chat, we created a list of best practices for admins to review in order to use the tool to its fullest capabilities. - -**During the conference:** -- At a minimum, send 3 announcements throughout the day to create awareness of any sessions, activations, contests, or parties you want to promote. -- Communicate with the Expensify Team in the #admins room if you see anything you have questions about or are unsure of to make sure we’re resolving issues together ASAP. -- As an admin, It’s up to you to help keep your conference community safe and respectful. [Flag any content for moderation](https://help.expensify.com/articles/other/Everything-About-Chat#flagging-content-as-offensive) that does not fit your culture and values to keep chatrooms a positive experience for everyone involved. - -**After the conference:** -- The rooms will all stay open after the conference ends, so encourage speakers to keep engaging as long as the conversation is going in their session room. -- Continue sharing photos and videos from the event or anything fun in #social as part of a wrap up for everyone. -- Use the #announce room to give attendees a sneak preview of your next event. diff --git a/docs/articles/other/Expensify-Chat-For-Conference-Attendees.md b/docs/articles/other/Expensify-Chat-For-Conference-Attendees.md deleted file mode 100644 index 3d30237dca5a..000000000000 --- a/docs/articles/other/Expensify-Chat-For-Conference-Attendees.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -title: Expensify Chat for Conference Attendees -description: Best Practices for Conference Attendees ---- - -## Overview -Expensify Chat is the best way to meet and network with other event attendees. No more hunting down your contacts by walking the floor or trying to find someone in crowds at a party. Instead, you can use Expensify Chat to network and collaborate with others throughout the conference. - -To help get you set up for a great event, we’ve created a guide to help you get the most out of using Expensify Chat at the event you’re attending. - -## Getting Started -We’ve rounded up some resources to get you set up on Expensify Chat and ready to start connecting with your fellow attendees: - -- [How to get set up and start using Expensify Chat](https://help.expensify.com/articles/other/Everything-About-Chat#how-to-use-chat-in-expensify) -- [How to format text in Expensify Chat](https://help.expensify.com/articles/other/Everything-About-Chat#how-to-format-text) -- [How to flag content and/or users for moderation](https://help.expensify.com/articles/other/Everything-About-Chat#flagging-content-as-offensive) - -## Chat Best Practices -To get the most out of your experience at your conference and engage people in a meaningful conversation that will fulfill your goals instead of turning people off, here are some tips on what to do and not to do as an event attendee using Expensify Chat: - -**Do:** -- Chat about non-business topics like where the best coffee is around the event, what great lunch options are available, or where the parties are happening that night! -- Share pictures of your travel before the event to hype everyone up, during the event if you met that person you’ve been meaning to see for years, or a fun pic from a party. -- Try to create fun groups with your fellow attendees around common interests like touring a local sight, going for a morning run, or trying a famous restaurant. - -**Don't:** -- Pitch your services in public rooms like #social or speaking session rooms. -- Start a first message with a stranger with a sales pitch. -- Discuss controversial topics such as politics, religion, or anything you wouldn’t say on a first date. -- In general just remember that you are still here for business, your profile is public, and you’re representing yourself & company, so do not say anything you wouldn’t feel comfortable sharing in a business setting. - -**Pro-Tips:** -Get active in Chat early and often by having real conversations around thought leadership or non-business discussions to stand out from the crowd! Also if you’re in a session and are afraid to ask a question, just ask in the chat room to make sure you can discuss it with the speaker after the session ends. - -By following these tips you’ll ensure that your messages will not be [flagged for moderation](https://help.expensify.com/articles/other/Everything-About-Chat#flagging-content-as-offensive) and you will not mess it up for the rest of us. diff --git a/docs/articles/other/Expensify-Chat-For-Conference-Speakers.md b/docs/articles/other/Expensify-Chat-For-Conference-Speakers.md deleted file mode 100644 index 5bd52425d92b..000000000000 --- a/docs/articles/other/Expensify-Chat-For-Conference-Speakers.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -title: Expensify Chat for Conference Speakers -description: Best Practices for Conference Speakers ---- - -## Overview -Are you a speaker at an event? Great! We're delighted to provide you with an extraordinary opportunity to connect with your session attendees using Expensify Chat — before, during, and after the event. Expensify Chat offers a powerful platform for introducing yourself and your topic, fostering engaging discussions about your presentation, and maintaining the conversation with attendees even after your session is over. - -## Getting Started -We’ve rounded up some resources to get you set up on Expensify Chat and ready to start connecting with your session attendees: - -- [How to get set up and start using Expensify Chat](https://help.expensify.com/articles/other/Everything-About-Chat#how-to-use-chat-in-expensify) -- [How to format text in Expensify Chat](https://help.expensify.com/articles/other/Everything-About-Chat#how-to-format-text) -- [How to flag content and/or users for moderation](https://help.expensify.com/articles/other/Everything-About-Chat#flagging-content-as-offensive) - -## Setting Up a Chatroom for Your Session: Checklist -To make the most of Expensify Chat for your session, here's a handy checklist: -- Confirm that your session has an Expensify Chat room, and have the URL link ready to share with attendees in advance. - - You can find the link by clicking on the avatar for your chatroom > “Share Code” > “Copy URL to dashboard” -- Join the chat room as soon as it's ready to begin engaging with your audience right from the start. -- Consider having a session moderator with you on the day to assist with questions and discussions while you're presenting. -- Include the QR code for your session's chat room in your presentation slides. Displaying it prominently on every slide ensures that attendees can easily join the chat throughout your presentation. - -## Tips to Enhance Engagement Around Your Session -By following these steps and utilizing Expensify Chat, you can elevate your session to promote valuable interactions with your audience, and leave a lasting impact beyond the conference. We can't wait to see your sessions thrive with the power of Expensify Chat! - -**Before the event:** -- Share your session's QR code or URL on your social media platforms, your website or other platforms to encourage attendees to join the conversation early on. -- Encourage attendees to ask questions in the chat room before the event, enabling you to tailor your session and address their specific interests. - -**During the event:** -- Keep your QR code readily available during the conference by saving it as a photo on your phone or setting it as your locked screen image. This way, you can easily share it with others you meet. -- Guide your audience back to the QR code and encourage them to ask questions, fostering interactive discussions. - -**After the event:** -- Continue engaging with attendees by responding to their questions and comments, helping you expand your audience and sustain interest. -- Share your presentation slides after the event as well as any photos from your session, allowing attendees to review and share your content with their networks if they want to. - -If you have any questions on how Expensify Chat works, head to our guide [here](https://help.expensify.com/articles/other/Everything-About-Chat). diff --git a/docs/articles/other/Expensify-Lounge.md b/docs/articles/other/Expensify-Lounge.md deleted file mode 100644 index 01a2d7a9e250..000000000000 --- a/docs/articles/other/Expensify-Lounge.md +++ /dev/null @@ -1,66 +0,0 @@ ---- -title: Welcome to the Expensify Lounge! -description: How to get the most out of the Expensify Lounge. ---- - - -# What is the Expensify Lounge? -The Expensify Lounge is a place where people go to Get Shit Done. It's a beautiful environment with great coffee and a group of people to collaborate with. Check out this guide on how to best utilize the Expensify Lounge! - -# The Two Rules -### Rule #1 - Get Shit Done - -The Lounge is a space for people to get work done. It is optimized to be the perfect environment for you to focus on your work, collaborate with others, and advance your most wild and creative ideas. To make this a reality, we ask our members to keep the following in mind: - -- **#focus** - Use the space for how it was designed and do not distract from others' focus. The space is beautiful, social, and collaborative, but it was created to help our members work effectively. -- **#urgency** - Working remotely is great, but there's nothing like real-time collaboration with your colleagues. Use the lounge to meet with co-workers IRL to continue the progress on whatever it is you're working on. -- **#results** - Don't mistake time for effort or effort for output. Upon arrival, visualize what you want to accomplish, and don't leave until it's done. - -## Rule #2 - Don’t Ruin it for Everyone Else - -We want this place to be incredible, innovative, and always elvoving. To achieve that, we have some general guidelines: - -- **#writeitdown** - If you can help others learn from you, do so. Write a blog post, a document, or a post in Expensify Chat to share with others. This includes making the Expensify Lounge a better space. Feel free to write down any improvements so we can make it better. -- **#showup** - If you are in the lounge, be fully present. Meet others, and collaborate in social rooms. The point is to build a community of people who are focused on getting shit done; you’ll get out what you put in. -- **#oneteam** - Providing an inclusive community is our priority, and we do not tolerate any form of discrimination. Aim to go out of your way to include people who want to be included. -- **#nocreeps** - Do not make people feel uncomfortable with your words or actions. If you are made to feel uncomfortable or notice this happening to someone else, you can use the escalation process outlined in the FAQ section. - -# How to Use the Expensify Lounge -Keeping those two rules in mind, below is a guide on how our members can get the most out of the lounge. - -### Rule #1 - Getting Shit Done -- **Order drinks from Concierge** - [Write Concierge here](https://new.expensify.com/concierge) to ask lounge questions or order beverages. Concierge will bring your order directly to you! -- **Using an office** - Offices are first come, first serve. If an office is open, feel free to use it! Please keep office use to under an hour. We currently do not allow reserving offices. -- **Lounge hours** - The lounge will be open from 8am-6pm PT, Monday through Friday and closed on some major holidays. You can review our Google Maps profile to check our holiday hours. -- **Make the lounge better** - Make any suggestions to improve the lounge experience in [#announce - Expensify Lounge](https://new.expensify.com/r/8292963527436014). - -## Rule #2 - Not Ruining it for Everyone Else -- **Offices are for calls** - Please do not occupy an office unless you have a call or collaborative meeting happening, and don't stay in an office for longer than an hour. -- **Respect other people** - Please do not be too loud or distracting while others are trying to work. While collaborating in Expensify Chat, be respectful of others’ viewpoints and keep a positive environment. -- **Stay home if you’re sick** - If you feel sick, please do not visit the lounge, or consider wearing a mask in public areas. -- **If you see something, say something** - If you are made to feel uncomfortable or witness others being made uncomfortable, let Concierge know. If this is happening in Expensify Chat, use our moderation tools (outlined below in the FAQ) to apply the applicable level of moderation. - -We’re so happy you are here to live rich, have fun, and save the world with us. Now, go enjoy the Expensify Lounge, and let's Get Shit Done! - -# FAQs - -#### What is Concierge? - -Concierge is our automated system that answers member questions in real-time. Questions regarding the local lounge will be routed directly to the lounge's Concierge. You can send Concierge a message if you have a drink request or general questions. They’ll take care of everything for you! - -#### Who is invited to the Expensify Lounge? - -Everyone is invited to the Expensify Lounge! Whether you're an existing customer, or you're someone looking for a great space to Get Shit Done, we'd love to have you. - -#### How do I escalate something that's making me or someone else uncomfortable? - -If you see something in Expensify Chat that should be escalated, you can use the escalation feature to mark a chat as: -- **Spam or Inconsiderate**: This will send a whisper to the sender of the message warning them of the violation, and the message will have a flag applied to it which will be visible to all users. Concierge will not review these flags. -- **Intimidating or Bullying**: The message will be immediately hidden, and the content will be reviewed by our team. After reviewing the message, and it's confirmed intimidation or bullying, the message will be permanently hidden and we'll communicate the violation to the sender of the message. -- **Harassment or Assault**: The message will be immediately hidden and reviewed by our team. The user will be sent a message to warning them of the violation, and Concierge can block the user if that's deemed necessary. - -If you witness something in-person, please write to Concierge referencing which lounge you are in, and they will escalate the issue appropriately. - -#### Where are other Expensify Lounge locations? - -Right now, we only have the San Francisco Lounge, but be on the lookout for more coming soon! diff --git a/docs/articles/other/Insights.md b/docs/articles/other/Insights.md deleted file mode 100644 index 682c2a251228..000000000000 --- a/docs/articles/other/Insights.md +++ /dev/null @@ -1,100 +0,0 @@ ---- -title: Custom Reporting and Insights -description: How to get the most out of the Custom Reporing and Insights ---- - -{% raw %} -# What is Custom Reporting and Insights? -The Insights dashboard allows you to monitor all aspects of company spend across categories, employees, projects, departments, and more. You can see trends in real time, forecast company budgets, and build unlimited custom reports with help from our trained specialist team. - -![Insights Pie Chart](https://help.expensify.com/assets/images/insights-chart.png){:width="100%"} -## Review your Insights data - -1. Navigate to your [Insights page](https://www.expensify.com/expenses?param={"fromInsightsTab":true,"viewMode":"charts"}), located in the left hand menu -2. Select a specific date range (the default view has the current month pre-selected) -3. Use the filter options to select the categories, tags, employees etc that you want insights on -4. Make sure that View in the top right corner is set to the pie chart icon -5. You can view any dataset in more detail by clicking in the “View Raw Data” column - -## Export your Insights data - -1. Switch the View in the top right corner of the [Insights page](https://www.expensify.com/expenses?param={"fromInsightsTab":true,"viewMode":"charts"}) to the lists icon -2. Select the expenses you want to export, either by selecting individual expenses, or checking the select all box (next to Date at the top) -3. Select **Export To** in the top right hand corner to download the report as a .csv file - -## Create a Custom Export Report for your Expenses - -1. Navigate to **Settings > Account > Preferences > scroll down to CSV Export Formats** -2. Build up a report using these [formulas](https://community.expensify.com/discussion/5795/deep-dive-expense-level-formula/p1?new=1) -3. Click the **Custom Export** button on the Insights page and your Account Manager will help get you started on building up your report - -## Create a Custom Export Report for your Policy - -1. Navigate to **Settings > Policies > Group > [Policy Name] > Export Formats** -2. Build up a report using these [formulas](https://community.expensify.com/discussion/5795/deep-dive-expense-level-formula/p1?new=1) -3. If you need any help, click the **Support** button on the top left to contact your Account Manager - -# FAQs - -#### Can I share my custom export report? - -If you would like to create a custom export report that can be shared with other policy admins, you can create these by navigating to the **[Settings > Policies > Group > [Policy Name] > Export Formats](https://www.expensify.com/admin_policies?param={"section":"group"})** page. Custom export reports created under **Settings > Account > Preferences** page are only available to the member who created them. - -#### Can I put expenses from different policies on the same report? - -Custom export reports created under Settings > Account > Preferences page are able to export expenses from multiple policies, and custom export formats created under Settings > Policies > Group > [Policy Name] > Export Formats are for expenses reported under that policy only. - -#### Are there any default export reports available? - -Yes! We have [seven default reports](https://community.expensify.com/discussion/5602/deep-dive-default-export-templates) available to export directly from the Reports page: - -- **All Data** - Expense Level Export** - the name says it all! This is for the people who want ALL the details from their expense reports. We're talking Tax, Merchant Category Codes, Approvers - you name it, this report's got it! -- **All Data** - Report Level Export - this is the report for those who don't need to see each individual expense but want to see a line by line breakdown at a report level - submitter, total amount, report ID - that kind of stuff -- **Basic Export** - this is the best way to get a simple breakdown of all your expenses - just the basics -- **Canadian Multiple Tax Export** - tax, GST, PST...if you need to know tax then this is the export you want! -- **Category Export** - want to see a breakdown of your expenses by Category? This is the export you -- **Per Diem Export** - the name says it all -- **Tag Export** - much like the Category Export, but for Tags - -*To note: these reports will be emailed directly to your email address rather than downloaded on your computer.* - -#### How many expenses can I export in one report? -The custom export reports are best for small-to-medium chunks of data. If you want to export large amounts of data, we recommend you use a [default export report](https://community.expensify.com/discussion/5602/deep-dive-default-export-templates) that you can run from the Reports page. - -#### What other kinds of export reports can my Account Manager help me create? - -We’ve built a huge variety of custom reports for customers, so make sure to reach out to your Account Manager for more details. Some examples of custom reports we’ve build for customers before are: - -- Accrual Report -- Aged Approval Reports -- Attendee Reporting -- Audit Report -- Candidate Spend -- Category Spend Report -- Department/Project Spend Report -- Duplication Report -- Duty of Care -- Efficiency -- Employee Bank Account Status -- Employee Details -- Employee Roles -- Expense Authorizations by Country -- Expense Reports by Country -- Expense Reports not posted to finance system -- Foreign Currency Transaction -- Fringe Benefit Tax Report -- HR Report -- Invoice Billed Transaction Reconciliation -- Mileage Reports -- Out of Pocket Expenses for Reimbursement -- Per Diem Report -- Reconciliation: Accounting, Bank Statement, Billed Transaction -- Rejected Report -- Travel Rule Class -- Travel Spend -- Unposted Cash Advance Report -- Unposted Procurement Aging Report -- Unposted Travel Aging Report -- Vendor Spend -- … or anything you can imagine! -{% endraw %} \ No newline at end of file diff --git a/docs/articles/other/Referral-Program.md b/docs/articles/other/Referral-Program.md deleted file mode 100644 index 1faff1c9ec4f..000000000000 --- a/docs/articles/other/Referral-Program.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -title: Expensify Referral Program -description: Send your joining link, submit a receipt or invoice, and we'll pay you if your referral adopts Expensify. ---- - - -# About - -Expensify has grown thanks to our users who love Expensify so much that they tell their friends, colleagues, managers, and fellow business founders to use it, too. - -As a thank you, every time you bring a new user into the platform who directly or indirectly leads to the adoption of a paid annual plan on Expensify, you will earn $250. - -# How to get paid for referring people to Expensify - -1. Submit a report or invoice, or share your referral link with anyone you know who is spending too much time on expenses, or works at a company that could benefit from using Expensify. - -2. You will get $250 for any referred business that commits to an annual subscription, has 2 or more active users, and makes two monthly payments. - -That’s right! You can refer anyone working at any company you know. - -If their company goes on to become an Expensify customer with an annual subscription, and you are the earliest recorded referrer of a user on that company’s paid Expensify Policy, you'll get paid a referral reward. - -The best way to start is to submit any receipt to your manager (you'll get paid back and set yourself up for $250 if they start a subscription: win-win!) - -Referral rewards for the Spring/Summer 2023 campaign will be paid by direct deposit. - -# FAQ - -- **How will I know if I am the first person to refer a company to Expensify?** - -Successful referrers are notified after their referral pays for 2 months of an annual subscription. We will check for the earliest recorded referrer of a user on the policy, and if that is you, then we will let you know. - -- **How will you pay me if I am successful?** - -In the Spring 2023 campaign, Expensify will be paying successful referrers via direct deposit to the Deposit-Only account you have on file. Referral payouts will happen once a month for the duration of the campaign. If you do not have a Deposit-Only account at the time of your referral payout, your deposit will be processed in the next batch. - -Learn how to add a Deposit-Only account [here](https://community.expensify.com/discussion/4641/how-to-add-a-deposit-only-bank-account-both-personal-and-business). - -- **I’m outside of the US, how do I get paid?** - -While our referral payouts are in USD, you will be able to get paid via a Wise Borderless account. Learn more [here](https://community.expensify.com/discussion/5940/how-to-get-reimbursed-outside-the-us-with-wise-for-non-us-employees). - -- **My referral wasn’t counted! How can I appeal?** - -Expensify reserves the right to modify the terms of the referral program at any time, and pays out referral bonuses for eligible companies at its own discretion. - -Please send a message to concierge@expensify.com with the billing owner of the company you have referred and our team will review the referral and get back to you. - -- **Where can I find my referral link?** - -Expensify members who are opted-in for our newsletters will have received an email containing their unique referral link. - -On the mobile app, go to **Settings** > **Invite a Friend** > **Share Invite Link** to retrieve your referral link. diff --git a/docs/articles/other/Your-Expensify-Account-Manager.md b/docs/articles/other/Your-Expensify-Account-Manager.md deleted file mode 100644 index 70e0435e00e1..000000000000 --- a/docs/articles/other/Your-Expensify-Account-Manager.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -title: Your Expensify Account Manager -description: Everything you need to know about Having an Expensify account manager ---- - - - -# What is an account manager? -An account manager is a dedicated point of contact to support policy admins with questions about their Expensify account. They are available to help you and other policy admins review your account, advise on best practices, and make changes to your policy on your behalf whenever you need a hand. They will actively monitor open technical issues and be proactive with recommendations to increase efficiency and minimize time spent on expense management. - -Unlike Concierge, an account manager’s support will not be real-time, 24 hours a day. A benefit of Concierge is that you get real-time support every day. Your account manager will be super responsive when online, but anything sent when they’re offline will not be responded to until they’re online again. - -For real-time responses and simple troubleshooting issues, you can always message our general support by writing to Concierge via the in-product chat or by emailing concierge@expensify.com. - -# How do I know if I have an account manager? -If you are a policy admin or domain admin, you will also hear from your account manager as soon as one gets assigned to your company. If you'd like a reminder who your account manager is, just click the Support link on the left side of Expensify - you'll see your account manager's name and photo, with an option to contact them for help. - -## How do I contact my account manager? -We make it easy to contact your account manager: - -1. Log in to your Expensify account, click "Support" along the left side of the page, and click the “Account Manager” option -2. Reply to (or click the chat link on) any email you get from your account manager -3. Sign in to new.expensify.com and go to the #admins room for any of your policies. Your account manager is in your #admin rooms ready to help you, so you can ask for help here and your account manager will respond in the chat. - -# FAQs -## Who gets an account manager? -Every customer with 10 or more paid subscribers is automatically assigned a dedicated account manager. If you have fewer than 10 active users each month, you can still get an account manager by increasing your subscription to 10 or more users, To get assigned an account manager immediately, log into your Expensify account and go to Settings > Policies > Group, then click Subscription and increase your subscription size to 10 or more. - -## How do I know if my account manager is online? -You will be able to see if they are online via their status, which will either say something like “online” or have their working hours. - -## What if I’m unable to reach my account manager? -If for some reason, you’re unable to reach your account manager, perhaps because they’re offline, then you can always reach out to Concierge for assistance. Your account manager will always get back to you when they’re online again. - -## Can I get on a call with my account manager? -Of course! You can ask your account manager to schedule a call whenever you think one might be helpful. We usually find that the most effective calls are those that deal with general setup questions. For technical troubleshooting, we typically recommend chat as that allows your account manager time to look into the issue, test things on their end, and, if needed, consult the wider Expensify technical team. It also allows you to easily refer back to instructions and links. diff --git a/docs/articles/other/Your-Expensify-Partner-Manager.md b/docs/articles/other/Your-Expensify-Partner-Manager.md deleted file mode 100644 index 9a68fbfd8b39..000000000000 --- a/docs/articles/other/Your-Expensify-Partner-Manager.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -title: Your Expensify Partner Manager -description: Everything you need to know about your Expensify Partner Manager ---- - - -# What is a Partner Manager? -A Partner Manager is a dedicated point of contact to support our ExpensifyApproved! Accountants with questions about their Expensify account. Partner Managers support our accounting partners by providing recommendations for client's accounts, assisting with firm-wide training, and ensuring partners receive the full benefits of our partnership program. They will actively monitor open technical issues and be proactive with recommendations to increase efficiency and minimize time spent on expense management. - -Unlike Concierge, a Partner Manager’s support will not be real-time, 24 hours a day. A benefit of Concierge is that you get real-time support every day. Your partner manager will be super responsive when online, but anything sent when they’re offline will not be responded to until they’re online again. - -For real-time responses and simple troubleshooting issues, you can always message our general support by writing to Concierge via the in-product chat or by emailing concierge@expensify.com. - -# How do I know if I have a Partner Manager? -For your firm to be assigned a Partner Manager, you must complete the [ExpensifyApproved! University](https://use.expensify.com/accountants) training course. Every external accountant or bookkeeper who completes the training is automatically enrolled in our program and receives all the benefits, including access to the Partner Manager. So everyone at your firm must complete the training to receive the maximum benefit. - -You can check to see if you’ve completed the course and enrolled in the ExpensifyApproved! Accountants program simply by logging into your Expensify account. In the bottom left-hand corner of the website, you will see the ExpensifyApproved! logo. - -# How do I contact my Partner Manager? -You can contact your Partner Manager by: -- Signing in to new.expensify.com and searching for your Partner Manager -- Replying to or clicking the chat link on any email you get from your Partner Manager - -# FAQs -## How do I know if my Partner Manager is online? -You will be able to see if they are online via their status in new.expensify.com, which will either say “online” or have their working hours. - -## What if I’m unable to reach my Partner Manager? -If you’re unable to contact your Partner Manager (i.e., they're out of office for the day) you can reach out to Concierge for assistance. Your Partner Manager will get back to you when they’re online again. - -## Can I get on a call with my Partner Manager? -Of course! You can ask your Partner Manager to schedule a call whenever you think one might be helpful. Partner Managers can discuss client onboarding strategies, firm wide training, and client setups. - -We recommend continuing to work with Concierge for **general support questions**, as this team is always online and available to help immediately. From 8a3f8292db2b828768f36b8e985f75cf79454529 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 20 Sep 2023 15:51:22 +0530 Subject: [PATCH 085/151] delete existing articles --- ...Expensify-Chat-Playbook-for-Conferences.md | 93 ------ ...ok-for-Small-to-Medium-Sized-Businesses.md | 283 ------------------ ...book-for-US-Based-Bootstrapped-Startups.md | 91 ------ ...laybook-for-US-based-VC-Backed-Startups.md | 208 ------------- 4 files changed, 675 deletions(-) delete mode 100644 docs/articles/playbooks/Expensify-Chat-Playbook-for-Conferences.md delete mode 100644 docs/articles/playbooks/Expensify-Playbook-for-Small-to-Medium-Sized-Businesses.md delete mode 100644 docs/articles/playbooks/Expensify-Playbook-for-US-Based-Bootstrapped-Startups.md delete mode 100644 docs/articles/playbooks/Expensify-Playbook-for-US-based-VC-Backed-Startups.md diff --git a/docs/articles/playbooks/Expensify-Chat-Playbook-for-Conferences.md b/docs/articles/playbooks/Expensify-Chat-Playbook-for-Conferences.md deleted file mode 100644 index 8f806bb03146..000000000000 --- a/docs/articles/playbooks/Expensify-Chat-Playbook-for-Conferences.md +++ /dev/null @@ -1,93 +0,0 @@ ---- -title: Expensify Chat Playbook for Conferences -description: Best practices for how to deploy Expensify Chat for your conference ---- -## Overview -To help make setting up Expensify Chat for your event and your attendees super simple, we’ve created a guide for all of the technical setup details. - -## Who you are -As a conference organizer, you’re expected to amaze and inspire attendees. You want attendees to get to the right place on time, engage with the speakers, and create relationships with each other that last long after the conference is done. Enter Expensify Chat, a free feature that allows attendees to interact with organizers and other attendees in realtime. With Expensify Chat, you can: - -- Communicate logistics and key information -- Foster conference wide attendee networking -- Organize conversations by topic and audience -- Continue conversations long after the event itself -- Digitize attendee social interaction -- Create an inclusive environment for virtual attendees - -Sounds good? Great! In order to ensure your team, your speakers, and your attendees have the best experience possible, we’ve created a guide on how to use Expensify Chat at your event. - -*Let’s get started!* - - -## Support -Connect with your dedicated account manager in any new.expensify.com #admins room. Your account manager is excited to brainstorm the best ways to make the most out of your event and work through any questions you have about the setup steps below. - -We also have a number of [moderation tools](https://help.expensify.com/articles/other/Everything-About-Chat#flagging-content-as-offensive) available to admins to help make sure your event is seamless, safe, and fun! - -## Step by step instructions for setting up your conference on Expensify Chat -Based on our experience running conferences atop Expensify Chat, we recommend the following simple steps: - -### Step 1: Create your event workspace in Expensify -To create your event workspace in Expensify: -1. In [new.expensify.com](https://new.expensify.com): “+” > “New workspace” -1. Name the workspace (e.g. “ExpensiCon”) - -### Step 2: Set up all the Expensify Chat rooms you want to feature at your event -**Protip**: Your Expensify account manager can complete this step with you. Chat them in #admins on new.expensify.com to coordinate! - -To create a new chat room: -1. Go to [new.expensify.com](https://new.expensify.com) -1. Go to “+” > New room -1. Name the room (e.g. “#social”) -1. Select the workspace created in step 1 -1. Select “Public” visibility -1. Repeat for each room - -For an easy-to-follow event, we recommend creating these chat rooms: - -- **#social** - This room will include all attendees, speakers, and members of your organizing team. You can use this room to discuss social events, happy hours, dinners, or encourage attendees to mingle, share photos and connect. -- **#announcements** - This room will be used as your main announcement channel, and should only be used by organizers to announce schedule updates or anything important that your attendees need to know. Everyone in your policy will be invited to this channel, but chatting in here isn’t encouraged so to keep the noise to a minimum. -- **Create an individual room for each session** - Attendees will be able to engage with the speaker/session leader and can ask questions about their content either before/during/after the session. -- **Create a room with your Expensify account manager/s** - We can use this room to coordinate using Expensify Chat before, during, and after the event. - -**Protip** Check out our [moderation tools](https://help.expensify.com/articles/other/Everything-About-Chat#flagging-content-as-offensive) to help flag comments deemed to be spam, inconsiderate, intimidating, bullying, harassment, assault. On any comment just click the flag icon to moderate conversation. - -### Step 3: Add chat room QR codes to the applicable session slide deck -Gather QR codes: -1. Go to [new.expensify.com](https://new.expensify.com) -1. Click into a room and click the room name or avatar in the top header -1. Go into Share Code -1. Screenshot the QR code to add to your deck - -Add the QR code to every slide so that if folks forget to scan the QR code at the beginning of the presentation, they can still join the discussion. - -### Step 4: Plan out your messaging and cadence before the event begins -Expensify Chat is a great place to provide updates leading up to your event -- share news, get folks excited about speakers, and let attendees know of crucial event information like recommended attire, travel info, and more. For example, you might consider: - -**Prep your announcements:** -- Create a document containing drafts of the key messages you intend to send throughout the day. -- If your event's agenda is broken up into hourly blocks, create a separate section for each hour of the event, to make it easy to find the correct section at the right time. -- Start each day with a review of the daily agenda, such as a bullet list summarizing what's happening hour by hour. - -**Post your updates:** -- Designate a team member to post each update in #announce at the designated time. -- Each hour, send a message listing exactly what is happening next – if there are multiple sessions happening simultaneously, list out each, along with a description of the session, a reminder of where it's located, and (most importantly) a link to the chat room for that session -- Write the messages in [markdown format](https://help.expensify.com/articles/other/Everything-About-Chat#how-to-format-text), such that they can be copy/pasted directly into Expensify Chat for sending. - - If there is some formatting issue upon posting, no problem: just edit the comment after sending, and it'll be fixed for everyone. -- We’d also recommend posting your updates on new lines so that if someone has a question about a certain item they can ask in a thread pertaining to that topic, rather than in one consolidated block. - -**Protip**: Your account manager can help you create this document, and would be happy to send each message at the appointed time for you. - -### Step 5: Share Expensify Chat How-To Resources with Speakers, Attendees, Admins -We’ve created a few helpful best practice docs for your speakers, admins, and attendees to help navigate using Expensify Chat at your event. Feel free to share the links below with them! - -- [Expensify Chat for Conference Attendees](https://help.expensify.com/articles/other/Expensify-Chat-For-Conference-Attendees) -- [Expensify Chat for Conference Speakers](https://help.expensify.com/articles/other/Expensify-Chat-For-Conference-Speakers) -- [Expensify Chat for Admins](https://help.expensify.com/articles/other/Expensify-Chat-For-Admins) - -### Step 6: Follow up with attendees after the event -Continue the connections by using Expensify Chat to keep your conference community connected. Encourage attendees to share photos, their favorite memories, funny stories, and more. - -## Conclusion -Once you have completed the above steps you are ready to host your conference on Expensify Chat! Let your account manager know any questions you have over in your [new.expensify.com](https://new.expensify.com) #admins room and start driving activity in your Expensify Chat rooms. Once you’ve reviewed this doc you should have the foundations in place, so a great next step is to start training your speakers on how to use Expensify Chat for their sessions. Coordinate with your account manager to make sure everything goes smoothly! diff --git a/docs/articles/playbooks/Expensify-Playbook-for-Small-to-Medium-Sized-Businesses.md b/docs/articles/playbooks/Expensify-Playbook-for-Small-to-Medium-Sized-Businesses.md deleted file mode 100644 index a4004dbe1b88..000000000000 --- a/docs/articles/playbooks/Expensify-Playbook-for-Small-to-Medium-Sized-Businesses.md +++ /dev/null @@ -1,283 +0,0 @@ ---- -title: Expensify Playbook for Small to Medium-Sized Businesses -description: Best practices for how to deploy Expensify for your business ---- -## Overview -This guide provides practical tips and recommendations for small businesses with 100 to 250 employees to effectively use Expensify to improve spend visibility, facilitate employee reimbursements, and reduce the risk of fraudulent expenses. - -- See our [US-based VC-Backed Startups](https://help.expensify.com/articles/playbooks/Expensify-Playbook-for-US-based-VC-Backed-Startups) if you are more concerned with top-line revenue growth - -## Who you are -As a small to medium-sized business owner, your main aim is to achieve success and grow your business. To achieve your goals, it is crucial that you make worthwhile investments in both your workforce and your business processes. This means providing your employees with the resources they need to generate revenue effectively, while also adopting measures to guarantee that expenses are compliant. - -## Step-by-step instructions for setting up Expensify -This playbook is built on best practices we’ve developed after processing expenses for tens of thousands of companies around the world. As such, use this playbook as your starting point, knowing that you can customize Expensify to suit your business needs. Every company is different, and your dedicated Setup Specialist is always one chat away with any questions you may have. - -### Step 1: Create your Expensify account -If you don't already have one, go to *[new.expensify.com](https://new.expensify.com)* and sign up for an account with your work email address. The account is free so don’t worry about the cost at this stage. - -> _Employees really appreciate how easy it is to use, and the fact that the reimbursement drops right into their bank account. Since most employees are submitting expenses from their phones, the ease of use of the app is critical_ -> -> **Robyn Gresham** -> Senior Accounting Systems Manager at SunCommon - -### Step 2: Create a Control Policy -There are three policy types, but for your small business needs we recommend the *Control Plan* for the following reasons: - -- *The Control Plan* is designed for organizations with a high volume of employee expense submissions, who also rely on compliance controls -- The ease of use and mobile-first design of the Control plan can increase employee adoption and participation, leading to better expense tracking and management. -- The plan integrates with a variety of tools, including accounting software and payroll systems, providing a seamless and integrated experience -- Accounting integrations include QuickBooks Online, Xero, NetSuite, and Sage Intacct, with indirect support from Microsoft Dynamics and any other accounting solution you work with - -We recommend creating one single policy for your US entity. This allows you to centrally manage all employees in one “group” while enforcing compliance controls and syncing with your accounting package accordingly. - -To create your Control Policy: - -1. Go to *Settings > Policies* -2. Select *Group* and click the button that says *New Policy* -3. Click *Select* under Control - -The Control Plan also gives you access to a dedicated Setup Specialist. You can find yours by looking at your policy's *#admins* room in *[new.expensify.com](https://new.expensify.com)*, and in your company’s policy settings in the *Overview* tab, where you can chat with them and schedule an onboarding call to walk through any setup questions. The Control Plan bundled with the Expensify Card is only *$9 per user per month* (not taking into account cash back your earn) when you commit annually. That’s a 75% discount off the unbundled price point if you choose to use a different Corporate Card (or no) provider. - -### Step 3: Connect your accounting system -As a small to medium-sized business, it's important to maintain proper spend management to ensure the success and stability of your organization. This requires paying close attention to your expenses, streamlining your financial processes, and making sure that your financial information is accurate, compliant, and transparent. Include best practices such as: - -- Every purchase is categorized into the correct account in your chart of accounts -- Receipts are sent to the accounting package to ensure visibility across the organization and to auditors -- Every expense is accounted for and added to your accounting system on time for your monthly accounts reconciliation. - -You do this by synchronizing Expensify and your accounting package as follows: - -1. Click *Settings > Policies* -2. Navigate to the *Connections* tab -3. Select your accounting system -4. Follow the prompts to connect your accounting package - -Check out the links below for more information on how to connect to your accounting solution: -- *[QuickBooks Online](https://community.expensify.com/discussion/4833/how-to-connect-your-policy-to-quickbooks-online)* -- *[Xero](https://community.expensify.com/discussion/5282/how-to-connect-your-policy-to-xero)* -- *[NetSuite](https://community.expensify.com/discussion/5212/how-to-connect-your-policy-to-netsuite-token-based-authentication)* -- *[Sage Intacct](https://community.expensify.com/discussion/4777/how-to-connect-to-sage-intacct-user-based-permissions-expense-reports)* -- *[Other Accounting System](https://community.expensify.com/discussion/5271/how-to-set-up-an-indirect-accounting-integration) - - -*“Employees really appreciate how easy it is to use, and the fact that the reimbursement drops right into their bank account. Since most employees are submitting expenses from their phones, the ease of use of the app is critical.”* -- Robyn Gresham, Senior Accounting Systems Manager at SunCommon - -### Step 4: Set category-specific compliance controls -Head over to the *Categories* tab to set compliance controls on your newly imported list of categories. More specifically, we recommend the following: - -1. First, enable *People Must Categorize Expenses*. Employees must select a category for each expense, otherwise, in most cases, it’s more work on you and our accounting connections will simply reject any attempt to export. -2. For more high-risk, travel-related categories, we recommend setting more strict compliance controls. For example, “Meals & Entertainment” should be set with the following: - - Receipts Required - - Descriptions Required, with Description Hints set - - Travel: “What is the business purpose of this expense?” - - Meals: “Could you share the business purpose, and tag attendees?” - - Entertainment: “Could you share the business purpose, and tag attendees?” -3. Disable any irrelevant expense categories that aren’t associated with employee spend -4. Configure *auto-categorization*, located just below your category list in the same tab. The section is titled *Default Categories*. Just find the right category, and match it with the presented category groups to allow for MCC (merchant category code) automated category selection with every imported connected card transaction. - -### Step 5: Make sure tags are required, or defaults are set -Tags in Expensify often relate to departments, projects/customers, classes, and so on. And in some cases they are *required* to be selected on every transactions. And in others, something like *departments* is a static field, meaning we could set it as an employee default and not enforce the tag selection with each expense. - -*Make Tags Required* -In the tags tab in your policy settings, you’ll notice the option to enable the “Required” field. This makes it so any time an employee doesn’t assign a tag to an expense, we’ll flag a violation on it and notify both the employee and the approver. - -- *Note:* In general, we take prior selection into account, so anytime you select a tag in Expensify, we’ll pre-populate that same field for any subsequent expense. It’s completely interchangeable, and there for convenience. - -*Set Tags as an Employee Default* -Separately, if your policy is connected to NetSuite or Sage Intacct, you can set departments, for example, as an employee default. All that means is we’ll apply the department (for example) that’s assigned to the employee record in your accounting package and apply that to every exported transaction, eliminating the need for the employee to have to manually select a department for each expense. - -### Step 6: Set rules for all expenses regardless of categorization -In the Expenses tab in your group Control policy, you’ll notice a *Violations* section designed to enforce top-level compliance controls that apply to every expense, for every employee in your policy. We recommend the following confiuration: - -*Max Expense Age: 90 days (or leave it blank)* -This will enable Expensify to catch employee reimbursement requests that are far too outdated for reimbursement, and present them as a violations. If you’d prefer a different time window, you can edit it accordingly - -*Max Expense Amount: $2,000 (or leave it blank)* -This is essentially like setting a daily or individual expense limitation on any employee, regardless of whether the transaction is reimbursable or non-reimbursable.This rule will enables Expensify to present larger expenses with a violation to notify both the submitter and approvers. - -*Receipt Required Amount: $75* -Receipts are important, and in most cases you prefer an itemized receipt. However, Expensify will generate an IRS-compliant electronic receipt (not itemized) for every expense not tied to hotels expense. For this reason, it’s important to enforce a rule where anytime an employee is on the road, and making business-related purchases at hotel (which happens a lot!), they are required to attach a physical receipt. - -![Expense Basics](https://help.expensify.com/assets/images/playbook-expense-basics.png){:width="100%"} - -At this point, you’ve set enough compliance controls around categorical spend and general expenses for all employees, such that you can put trust in our solution to audit all expenses up front so you don’t have to. Next, let’s dive into how we can comfortably take on more automation, while relying on compliance controls to capture bad behavior (or better yet, instill best practices in our employees). - -### Step 7: Set up scheduled submit -For an efficient company, we recommend setting up [Scheduled Submit](https://community.expensify.com/discussion/4476/how-to-enable-scheduled-submit-for-a-group-policy) on a *Daily* frequency: - -- Click *Settings > Policies* -- From here, select your group collect policy -- Within your policy settings, select the *Reports* tab -- You’ll notice *Scheduled Submit* is located directly under *Report Basics* -- Choose *Daily* - -Between Expensify's SmartScan technology, automatic categorization, and [DoubleCheck](https://community.expensify.com/discussion/5738/deep-dive-how-does-concierge-receipt-audit-work) features, your employees shouldn't need to do anything more than swipe their Expensify Card or take a photo of their receipt. - -Expenses with violations will stay behind for the employee to fix, while expenses that are “in-policy” will move into an approver’s queue to mitigate any potential for delays. Scheduled Submit will ensure all expenses are submitted automatically for approval. - -![Scheduled submit](https://help.expensify.com/assets/images/playbook-scheduled-submit.png){:width="100%"} - -> _We spent twice as much time and effort on expenses without getting nearly as accurate of results as with Expensify._ -> -> Kevin Valuska -> AP/AR at Road Trippers - -### Step 8: Connect your business bank account (US only) -If you’re located in the US, you can utilize Expensify’s payment processing and reimbursement features. - -*Note:* Before you begin, you’ll need the following to validate your business bank account: - -1. Your bank account credentials -2. A form of ID (a driver’s license or passport) -3. Your business tax ID number, your business’ address and your website URL - -Let’s walk through the process of linking your business bank account: - -1. Go to *Settings > Account*, and select the *Payments* tab -2. Select *Add Verified Bank Account* -3. From here, we’ll ask you to use your online banking credentials to connect to your bank (Note that this must be the account owner or admin credentials) -- Alternatively, you can go the more manual route by selecting “Connect Manually” -4. Once that’s done, we’ll collect all of the necessary information on your business, such as your legal business name and address -5. We’ll then collect your personal information, and a photo ID to confirm your identity - -You only need to do this once: you are fully set up for not only reimbursing expense reports, but issuing Expensify Cards, collecting customer invoice payments online (if applicable), as well as paying supplier bills online. - -### Step 9: Invite employees and set an approval workflow -*Select an Approval Mode* -We recommend you select *Advanced Approval* as your Approval Mode to set up a middle-management layer of a approval. If you have a single layer of approval, we recommend selecting [Submit & Approve](https://community.expensify.com/discussion/5643/deep-dive-submit-and-approve). But if *Advanced Approval* if your jam, keep reading! - -*Import your employees in bulk via CSV* -Given the amount of employees you have, it’s best you import employees in bulk via CSV. You can learn more about using a CSV file to bulk upload employees with *Advanced Approval [here](https://community.expensify.com/discussion/5735/deep-dive-the-ins-and-outs-of-advanced-approval)* - -![Bulk import your employees](https://help.expensify.com/assets/images/playbook-impoort-employees.png){:width="100%"} - -*Manually Approve All Reports* -In most cases, at this stage, approvers prefer to review all expenses for a few reasons. 1) We want to make sure expense coding is accurate, and 2) We want to make sure there are no bad actors before we export transactions to our accounting system. - -In this case we recommend setting *Manually approve all expenses over: $0* - -### Step 10: Configure Auto-Approval -Knowing you have all the control you need to review reports, we recommend configuring auto-approval for *all reports*. Why? Because you’ve already put reports through an entire approval workflow, and manually triggering reimbursement is an unnecessary action at this stage. - -1. Navigate to *Settings > Policies > Group > [Policy Name] > Reimbursement* -2. Set your *Manual Reimbursement threshold to $20,0000* - -### Step 11: Enable Domains and set up your corporate card feed for employees -Expensify is optimized to work with corporate cards from all banks – or even better, use our own perfectly integrated *[Expensify Card](https://use.expensify.com/company-credit-card)*. The first step for connecting to any bank you use for corporate cards, and the Expensify Card is to validate your company’s domain in Domain settings. - -To do this: - -- Click *Settings* -- Then select *Domains* - -#### If you have an existing corporate card -Expensify supports direct card feeds from most financial institutions. Setting up a corporate card feed will pull in the transactions from the connected cards on a daily basis. To set this up, do the following: - -1. Go to *Company Cards >* Select your bank - - If you don’t see your financial institution in the list of banks we support, you can review an alternative solution in the Feature Deep Dives section below -2. Next, enter your bank account login credentials. - - To successfully connect to your bank, we’ll need the *master admin (primary) account* login credentials. -3. Next, assign the corporate cards to your employees by selecting the employee’s email address and the corresponding card number from the two drop-down menus under the *Assign a Card* section -4. Set a transaction start date (this is really important to avoid pulling in multiple outdated historical expenses that you don’t want employees to submit) - -![If you have existing corporate cards](https://help.expensify.com/assets/images/playbook-existing-corporate-card.png){:width="100%"} - -As mentioned above, we’ll be able to pull in transactions as they post (daily) and handle receipt matching for you and your employees. One benefit of the Expensify Card for your company is being able to see transactions at the point of purchase which provides you with real-time compliance. We even send users push notifications to SmartScan their receipt when it’s required and generate IRS-compliant e-receipts as a backup wherever applicable. - -#### If you don't have a corporate card, use the Expensify Card (US only) -Expensify provides a corporate card with the following features: - -- Up to 2% cash back (up to 4% in your first 3 months!) -- [SmartLimits](https://community.expensify.com/discussion/4851/deep-dive-what-are-unapproved-expense-limits#latest) to control what each individual cardholder can spend -- A stable, unbreakable real-time connection (third-party bank feeds can run into connectivity issues) -- Receipt compliance - informing notifications (eg. add a receipt!) for users *as soon as the card is swiped* -- A 50% discount on the price of all Expensify plans -- Multiple discounts and savings on a host of partner tech suppliers -- Good Karma - 10% of all card interchange we earn goes directly to the Expensify.org Social Justice Community funds - -The Expensify Card is recommended as the most efficient way to manage your company's spending. - -Here’s how to enable it: - -1. There are *two ways* you can [apply for the Expensify Card](https://community.expensify.com/discussion/4874/how-to-apply-for-the-expensify-card) - - *Via your Inbox* - - *Via Domain Settings* - Go to Settings > Domain > Company Cards > Enable Expensify Card -2. Assign the cards to your employees -3. Set *SmartLimits*: - - *Employees* - We recommend a low limit for most employees, roughly double the size of the maximum daily spend – such as $1000. - - *Execs* - We recommend a higher limit for executives, roughly 10x the limit of a non-executive employee (eg, $10,000). - -Once the Expensify Cards have been assigned, each employee will be prompted to enter their mailing address so they can receive their physical card. In the meantime, a virtual card will be ready to use immediately. - -If you have an accounting system we directly integrate with, check out how we take automation a step further with [Continuous Reconciliation](https://community.expensify.com/discussion/7335/faq-what-is-the-expensify-card-auto-reconciliation-process). We’ll create an Expensify Card clearing and liability account for you. Each time settlement occurs, we’ll take the total amount of your purchases and create a journal entry that credits the settlement account and debits the liability account - saving you hours of manual reconciliation work at the end of your statement period. - -### Step 12: Set up Bill Pay and Invoicing -As a small business, managing bills and invoices can be a complex and time-consuming task. Whether you receive bills from vendors or need to invoice clients, it's important to have a solution that makes the process simple, efficient, and cost-effective. - -Here are some of the key benefits of using Expensify for bill payments and invoicing: -- Flexible payment options: Expensify allows you to pay your bills via ACH, credit card, or check, so you can choose the option that works best for you (US businesses only). -- Free, No Fees: The bill pay and invoicing features come included with every policy and workspace, so you won't need to pay any additional fees. -- Integration with your business bank account: With your business bank account verified, you can easily link your finances to receive payment from customers when invoices are paid. - -Let’s first chat through how Bill Pay works - -1. Have your vendors send their invoices to yourdomain.com@expensify.cash. -- This email address comes with every account, so no need to activate it anywhere. -2. Once the invoicehas been received, we’ll create a bill to pay for your review directly in Expensify -3. At the top of the bill, you’ll notice a Pay button. Once you click that, you’ll see options including ACH, credit/debit card, along with mailing a physical check. - -Similarly, you can send bills directly from Expensify as well. - -1. From the *Reports* tab, select the down arrow next to *New Report* and select *Bill* -2. Next, enter the Supplier’s email address, the Merchant name, the total amount, and the date -3. At this point, you can also upload an attachment to further validate the bill if necessary -4. Click *Submit*, we’ll forward the newly created bill directly to your Supplier. - -![Send bills directly from Expensify](https://help.expensify.com/assets/images/playbook-new-bill.png){:width="100%"} - -Reports, invoices, and bills are largely the same, in theory, just with different rules. As such, creating a customer invoice is just like creating an expense report and even a bill. - -1. From the *Reports* tab, select the down arrow next to *New Report* and select *Invoice*. -2. Add all of the expenses/transactions tied to the Invoice -3. Enter the recipient’s email address, a memo if needed, and a due date for when it needs to get paid, and click *Send* - -You’ll notice it’s a slightly different flow from creating a Bill. Here, you are adding the transactions tied to the Invoice, and establishing a due date for when it needs to get paid. If you need to apply any markups, you can do so from your policy settings under the Invoices tab. Your customers can pay their invoice in Expensify via ACH, or Check, or Credit Card. - -### Step 13: Run monthly, quarterly and annual reporting -At this stage, reporting is important and given that Expensify is the primary point of entry for all employee spend, we make reporting visually appealing and wildly customizable. - -1. Head to the *Expenses* tab on the far left of your left-hand navigation -2. Select the pie chart icon on the right top right corner -3. Configure the filters to your preference - -We recommend reporting: - -- *Monthly* - for spend analysis on your GLs, active projects and department spend -- *Quarterly* - for budget comparison reporting. Pull up your BI tool and compare your active budgets with your spend reporting here in Expensify -- *Annually* - Run annual spend trend reports with month-over-month spend analysis, and prepare yourself for the upcoming fiscal year. - -![Expenses!](https://help.expensify.com/assets/images/playbook-expenses.png){:width="100%"} - -### Step 14: Set your Subscription Size and Add a Payment card -Our pricing model is unique in the sense that you are in full control of your billing. Meaning, you have the ability to set a minimum number of employees you know will be active each month and you can choose which level of commitment fits best. We recommend setting your subscription to *Annual* to get an additional 50% off on your monthly Expensify bill. In the end, you've spent enough time getting your company fully set up with Expensify, and you've seen how well it supports you and your employees. Committing annually just makes sense. - -To set your subscription, head to: - -1. Settings > Policies -2. Select *Group* -3. Scroll down to *Subscription* -4. Select *Annual Subscription* -5. Enter the number of employees you know will be active each month -6. Enable *Auto-Renew* and *Auto-Increase Subscription Size* - -Now that we’ve gone through all of the steps for setting up your account, let’s make it official so there are no interruptions in service as your employees begin using Expensify. We handle payments for our service via a paymentcard, and to add one: - -1. Go to *Account > Settings > Payments* -2. Select *Add Payment Card* -3. Enter your name, card number, postal code, expiration and CVV -4. Click *Accept Terms* - -## You’re all set! -Congrats, you are all set up! If you need any assistance with anything mentioned above or would like to understand other features available in Expensify, reach out to your Setup Specialist directly in *[new.expensify.com](https://new.expensify.com)*. Don’t have one yet? Create a Control Policy, and we’ll automatically assign a dedicated Setup Specialist to you. diff --git a/docs/articles/playbooks/Expensify-Playbook-for-US-Based-Bootstrapped-Startups.md b/docs/articles/playbooks/Expensify-Playbook-for-US-Based-Bootstrapped-Startups.md deleted file mode 100644 index 089ad16834ac..000000000000 --- a/docs/articles/playbooks/Expensify-Playbook-for-US-Based-Bootstrapped-Startups.md +++ /dev/null @@ -1,91 +0,0 @@ ---- -title: Expensify Playbook for US-Based Bootstrapped Startups -description: Best practices for how to deploy Expensify for your business ---- - -This playbook details best practices on how Bootstrapped Startups with less than 5 employees can use Expensify to prioritize product development while capturing business-related receipts for future reimbursement. -- See our *[Playbook for VC-Backed Startups](https://help.expensify.com/articles/playbooks/Expensify-Playbook-for-US-based-VC-Backed-Startups)* if you have taken venture capital investment and are more concerned with prioritizing top-line revenue growth than achieving near-term profitability -- See our *[Playbook for Small to Medium-Sized Businesses](https://help.expensify.com/articles/playbooks/Expensify-Playbook-for-Small-to-Medium-Sized-Businesses)* if you are more concerned with maintaining profitability than growing top-line revenue. - -# Who you are -As a bootstrapped startup, you have surrounded yourself with a small handful of people you trust, and are focused on developing your concept to officially launch and possibly take to outside investors. You are likely running your business with your own money, and possibly a small amount of funding from friends and family. You are either paying yourself a little, or not at all, but at this stage, the company isn’t profitable. And for now, you are capturing receipts so that you can reimburse yourself for startup costs when you either take on investment or start to turn a profit. - -# Step-by-step instructions for setting up Expensify -This playbook is built based on best practices we’ve developed after processing expenses for tens of thousands of companies around the world. As such, use this playbook as your starting point, knowing that you can customize Expensify to suit your business needs. Every company is different, and we’re always one chat away with any questions you may have. - -## Step 1: Create your Expensify account -If you don't already have one, go to *[new.expensify.com](https://new.expensify.com)* and sign up for an account with your business email address. - -## Step 2: Create a Free Plan Workspace -There are three plans (Free, Collect, and Control), but for your needs, we recommend the *Free Plan* for the following reasons: - -- You are looking to capture receipts, but you don’t need to submit expenses to anyone for approval or reimbursement -- You are a small, highly collaborative group and project-oriented Chat functionality can help you stay organized -- When your business produces enough cash that you can pay yourself, you might want to reimburse yourself for the expenses you’ve captured - -To create your Free Plan Workspace: - -1. Go to *[new.expensify.com](https://new.expensify.com)* -2. Select *New Workspace* -3. Select the avatar in the top left corner -4. Click on the workspace name and rename it -5. Express yourself by adding an avatar image, preferably an image of yourself - -The Free Plan also gives you direct access to lightning-fast 24/7 support via Concierge. Within *[new.expensify.com](https://new.expensify.com/concierge)*, you can start a direct message (DM) with Concierge anytime. - -## Step 3: Invite Your Team -As a bootstrapped startup, you communicate with your team all day. Similarly, if you are a co-founder, you will have multiple people that will need to capture receipts for your project. - -1. Click on your avatar -2. Select *Workspaces* -3. Click on your workspace -4. Select *Members* -5. Click *Invite*, and enter each team member’s email address - -By inviting your team, all members of your team will have access to unlimited receipt capture via SmartScan, and you’ll all have access to our free chat tool, which makes it easy to chat about your project and the expenses you’ve captured to help accelerate your project. - -## Step 4: Link Your Business Bank Account (Optional) -If you have a business bank account at this stage, congrats! You might be better suited to check out our Seed Stage playbook. If you are located in the US, you can unlock many great features by linking your US-based business bank account such as paying bills via Expensify, along with the Expensify Card. - -Here’s how to set it up: - -1. Click on your *avatar* -2. Select *Workspaces* -3. Click on your workspace name -4. At the bottom, select *Bank account* -5. Select your bank and enter your online login credentials - -Once this is done, you are all set to begin the process of enabling the Expensify Card. Not just for you, but if you have a co-founder, you can also issue them a card. - -## Step 5: Get the Expensify Card -If you linked your business bank account in the previous step, you are immediately eligible for the Expensify Card. The Expensify Card is included with your Free Plan workspace, it earns you up to 2% cash back (up to 4% in your first 3 months!), and they are all stored in your team’s workspace. It’s easy to apply and it takes minutes! - -Here’s how to enable the Expensify Card: - -1. Click on your *avatar* -2. Select *Workspaces* -3. Click on your workspace -4. Select *Cards* -5. Next, you’ll be redirected to expensify.com -6. Set a SmartLimit > $0 -7. We’ll also ask you for your mailing address to send you a physical Expensify Card - -You’ll be issued a virtual card that you can use immediately, however, in 1-3 business days your Expensify Card will arrive. You can use the Expensify Card anywhere Visa is accepted. - -## Step 6: View and Pay Bills -Whether it’s online services or vendors you work with to grow your project, you likely have bills to pay. And now that you have your business bank account linked, you can easily pay bills directly from Expensify. Seeing as these are project-related expenses, we’ll capture them for you for future reference directly within your project’s workspace. - -There’s no actual setup required. Your free plan comes with a _yourdomain.com@expensify.com_ email address. Just have all of your vendors send bills to that email address. - -To view and pay bills: - -1. Click on your *avatar* -2. Select *Workspaces* -3. Click on your workspace -4. Select *Bills* - -When you have bills to pay you can click *View all bills* under the *Manage your bills* box and we’ll keep a neatly organized list of all of the bills you can pay via ACH directly from your Expensify account. - -# You’re all set! -Congrats, you are all set up! If you need any assistance with anything mentioned above, reach out to either your Concierge directly in *[new.expensify.com](https://new.expensify.com/concierge)*, or email concierge@expensify.com. Create a Collect or Control Policy, and we’ll automatically assign a dedicated Setup Specialist to you. - diff --git a/docs/articles/playbooks/Expensify-Playbook-for-US-based-VC-Backed-Startups.md b/docs/articles/playbooks/Expensify-Playbook-for-US-based-VC-Backed-Startups.md deleted file mode 100644 index 501d2f1538ef..000000000000 --- a/docs/articles/playbooks/Expensify-Playbook-for-US-based-VC-Backed-Startups.md +++ /dev/null @@ -1,208 +0,0 @@ ---- -title: Expensify Playbook for US-Based VC-Backed Startups -description: Best practices for how to deploy Expensify for your business ---- -This playbook details best practices on how Seed to Series A startups with under 100 employees can use Expensify to prioritize top-line revenue growth while managing spend responsibly. - -- See our Playbook for Bootstrapped Businesses if you haven't taken any VC money yet. -- See our Playbook for Small Businesses if you are more concerned with maintaining profitability than growing top-line revenue. [Coming soon…] -- See our Playbook for Midsize Businesses if you are series B or beyond, or have more than 100 employees. [Coming soon…] - -# Who you are -As a VC-backed business focused on growth and efficiency, you are looking for a product that puts smart automation in your hands. You prioritize top-line revenue growth over cash conservation, understanding that you’ll need to spend in order to grow. As a result, you want to enable your employees by putting spending power in their hands responsibly so there are appropriate compliance controls in place that scale with the business growth. Not only that, you want to decrease the amount of time you spend at the end of each month reimbursing employees, reconciling spend, and closing your books. - -# Step-by-step instructions for setting up Expensify -This playbook is built based on best practices we’ve developed after processing expenses for tens of thousands of companies around the world. As such, use this playbook as your starting point, knowing that you can customize Expensify to suit your business needs. Every company is different, and we’re always one chat away with any questions you may have. - -## Step 1: Create your Expensify account -If you don't already have one, go to *[new.expensify.com](https://new.expensify.com)* and sign up for an account with your work email address. The account is free so don’t worry about the cost at this stage. - -## Step 2: Create a Control Policy -There are three policy types, but for your needs we recommend the Control Policy for the following reasons: - -- You can cap spend on certain expense types, and set compliance controls so Expensify’s built-in Concierge Audit Tracking can detect violations on your behalf -- As a growing business with VC-funding, the Control plan will scale with you as your team grows and you start to introduce more sophisticated approval workflows - -To create your Control Policy: - -1. Go to *Settings > Policies* -2. Select *Group* and click the button that says *New Policy* -3. Click *Select* under Control - -The Control plan also gives you access to a dedicated Setup Specialist. You can find yours by looking at your policy's #admins room at *[new.expensify.com](https://new.expensify.com)*, and chatting with them there. The Control plan is bundled with the Expensify Card is $9 per user per month when you commit annually, which is a 75% discount off our standard unbundled price point. The Control plan also gives you access to a dedicated Setup Specialist. You can find yours by looking at your policy's *#admins* room in *[new.expensify.com](https://new.expensify.com)*, and chat with them there. - -## Step 3: Connect your accounting system -As a VC-backed company, your investors will want to see that your books are managed properly. That means making sure that: - -- Every purchase is categorized into the correct account in your chart of accounts -- Every expense is accounted for and added to your accounting system - -You do this by synchronizing Expensify and your accounting package as follows: - -1. Click *Settings > Policies* -2. Navigate to the *Connections* tab -3. Select your accounting system - - If you don’t see your accounting solution in the list of integrations we support, you can review an alternative solution in the Feature Deep Dives section below. -4. Follow the prompts to connect your accounting package - - Detailed instructions on connecting your accounting package are linked on the Connections page -5. Once connected, your categories will sync, and you’re ready to set Category Rules - -_“Expensify syncs seamlessly with QuickBooks, supports our web-based, paperless workflow, and offers internal controls, so it was the natural choice.”_ - _- Laura Redmond, CEO of Redmond Accounting_ - -## Step 4: Set up category rules -[Category rules](https://community.expensify.com/discussion/4638/how-to-enable-category-specific-rules-and-descriptions) are how you provide employees hints and requirements to make sure purchases stay within reasonable ranges and are documented appropriately for approval. For your company size and stage, we recommend the following: - -1. Click *Settings > Policies* -2. Navigate to the *Categories* tab where you’ll see all the categories you just imported from your accounting package -3. To set a rule for a specific category, click *“Edit Rules”* -4. The Edit Rules section will provide several expense category rules that tie to specific general ledger categories. While the individual rules might change slightly from business to business, and the exact category name will depend on your specific chart of accounts, we recommend these settings for VC backed startups: - - Set a $75 daily limit on meals and entertainment purchases - - Though we recommend [Expensify Guaranteed eReceipts](https://community.expensify.com/discussion/5542/deep-dive-what-are-ereceipts) for most purchases, for large purchases or those in categories most often associated with fraud, we recommend scanned receipts for extra protection: - - For any purchase over $1000 - - For all lodging purchases, regardless of size - - For any meal over $50/person - - For all office supplies - - For all software purchases - - For all airfare purchases - - Require manual explanations for certain high risk categories: - - For airfare expenses a description of the expense mandatory for the employee to include the purpose of the travel - - Require a description for all rideshare and taxi expenses, ensuring employees are listing a purpose for the expense - -Setting up these category rules allows you to concentrate on growth versus needing to micromanage your employees' spending. -## Step 5: Set up scheduled submit -For an efficiency-focused company, we recommend setting up [Scheduled Submit](https://community.expensify.com/discussion/4476/how-to-enable-scheduled-submit-for-a-group-policy) on a Daily frequency: - -1. Click *Settings > Policies* -2. From here, select your group Control policy -3. Within your policy settings, select the *Reports* tab -4. You’ll notice *Scheduled Submit* is located directly under *Report Basics* -5. Choose *Daily* - -Between Expensify's SmartScan technology, direct corporate card feed import, automatic categorization, and [DoubleCheck](https://community.expensify.com/discussion/5738/deep-dive-how-does-concierge-receipt-audit-work) features, your employees shouldn't need to do anything more than swipe their Expensify Card or scan their receipt. - -Scheduled Submit will ensure all expenses are submitted automatically. Any expenses that do not fall within the rules you’ve set up for your policy will be escalated to you for manual review. - -_“Our employees just SmartScan a receipt as soon as they receive it, and regardless of what currency it's in, we process the expense and issue reimbursement automatically.”_ -_- Amina Mobasher, Accountant at Ideo.org_ - -## Step 6: Connect your business bank account -If you’re located in the US, you can utilize Expensify’s payment processing and reimbursement features. - -*Note:* Before you begin, you’ll need the following to validate your business bank account: - -- Your bank account credentials -- A form of ID (a driver’s license or passport) -- Your business tax ID number, your business’ address and your website URL - -Let’s walk through the process of linking your business bank account: - -1. Go to *Settings > Account*, and select the *Payments* tab -2. Select *Add Verified Bank Account* -3. From here, we’ll ask you to use your online banking credentials to connect to your bank - - Alternatively, you can go the more manual route by selecting “Connect Manually” -4. Once that’s done, we’ll collect all of the necessary information on your business, such as your legal business name and address -5. We’ll then collect your personal information, and a photo ID to confirm your identity - -You only need to do this once. You are fully set up for not only reimbursing expense reports, but issuing Expensify Cards, collecting invoice payments online, as well as paying bills online. - -## Step 7: Invite employees -Next, you’ll want to invite your employees to the company policy you created. You can invite employees under *Settings > Policies > Policy Name > People*. From there, you can add employees one of three ways: - -- [Unique Policy Link](https://community.expensify.com/discussion/4643/how-to-invite-people-to-your-policy-using-a-join-link) - Each policy has a unique policy invite link, which is located at the top of the People tab in your policy settings. Simply share that link with anyone you’d like to add to your policy. -- [Manually](https://community.expensify.com/discussion/4975/how-to-invite-users-to-your-policy-manually-or-in-bulk/p1?new=1) - Enter employee email addresses manually by clicking the green Invite button in the People tab of your policy -- [Google SSO](https://community.expensify.com/discussion/4774/how-to-enable-google-apps-sso-with-your-expensify-group-policy) - Or, if you have a Google Workspace configured, you can synchronize your policy's people list to match your Google Workspace employee list. - -In the next section, we’ll go through how to configure approval routing but it’s important to remember that you’ll always have these 3 options to utilize, specifically the unique policy link and manual invites as your team continues to grow. - -## Step 8: Set up an approval workflow -Now, let’s set up some approval rules for your business as well as the ideal approval workflow that employee reports will follow after report submission: - -1. Go to *Settings > Policies*, and select the *People* tab. -2. From there, select [Submit & Approve](https://community.expensify.com/discussion/5643/deep-dive-submit-and-approve) - this will automatically add you as the approver, which ensures that any expenses that fall outside of the rules you set for your policy are brought to your attention. - - *Note*: If you are over 50 employees, please ask your Guide about the benefits of setting up an Advanced Approval workflow. -3. Next, enable manual approval for *expenses over $1000*. - - *Note*: We do not recommend configuring random report auditing for companies of your stage and scale. -4. Next, enable *Workflow Enforcement*. - - This ensures that employees are required to submit to you and not to someone else. -5. Disable *Prevent Self-Approval*. This is a more powerful feature recommended for companies with advanced compliance requirements, but generally isn't recommended for a company of your scale. - -Thanks to our [Concierge Receipt audit technology](https://community.expensify.com/discussion/5738/deep-dive-how-does-concierge-receipt-audit-work), once you set up an approval workflow, most expenses will be audited automatically and won’t require manual review. Your time is valuable, so you should focus it on reviewing only the expenses that fall outside of your policy’s rules. - -## Step 9: Set up your corporate card and assign cards to employees -Expensify is optimized to work with corporate cards from all banks – or even better, use our own perfectly integrated Expensify Card. - -### If you have an existing corporate card -Expensify supports direct card feeds from most financial institutions. Setting up a corporate card feed will pull in the transactions from the connected cards on a daily basis. To set this up, do the following: - -1. Go to *Settings > Domains > Company Cards >* Select your bank - - If you don’t see your financial institution in the list of banks we support, you can review an alternative solution in the Feature Deep Dives section below -2. Next, enter your bank account login credentials. - - To successfully connect to your bank, we’ll need the *master admin (primary) account* login credentials. -3. Next, assign the corporate cards to your employees by selecting the employee’s email address and the corresponding card number from the two drop-down menus under the *Assign a Card* section -4. Set a transaction start date - - If you don’t have a backlog of transactions you’d like to account for, feel free to skip this step. - -As mentioned above, we’ll be able to pull in transactions as they post (daily) and handle receipt matching for you and your employees. However, with the Expensify Card, we’re able to bring in transactions at the point of sale which provides you with real-time compliance. Next, let’s dive into how to set up the Expensify Card and the benefits of using the Expensify Card. - -### If you don't have a corporate card, use the Expensify Card -Expensify provides a corporate card with the following features: - -- Up to 2% cash back (up to 4% in your first 3 months!) -- [SmartLimits](https://community.expensify.com/discussion/4851/deep-dive-what-are-unapproved-expense-limits#latest) -- A stable, unbreakable connection (third-party bank feeds can run into connectivity issues) - -The Expensify Card is recommended as the most efficient way to manage your company's spending. - -Here’s how to enable it: - -1. There are *two ways* you can [apply for the Expensify Card](https://community.expensify.com/discussion/4874/how-to-apply-for-the-expensify-card) - - *Via your Inbox* - - *Via Domain Settings* - Go to Settings > Domain > Company Cards > Enable Expensify Card -2. Assign the cards to your employees -3. Set *SmartLimits*: - - *Employees* - We recommend a low limit for most employees, roughly double the size of the maximum daily spend – such as $1000. - - *Execs* - We recommend a higher limit for executives, roughly 10x the limit of a non-executive employee (eg, $10,000). - -Once the Expensify Cards have been assigned, each employee will be prompted to enter their mailing address so they can receive their physical card. In the meantime, a virtual card will be ready to use immediately. - -If you have an accounting system we directly integrate with, check out how we take automation a step further with [Auto-Reconciliation](https://community.expensify.com/discussion/7335/faq-what-is-the-expensify-card-auto-reconciliation-process). We’ll create an Expensify Card clearing and liability account for you. Each time settlement occurs, we’ll take the total amount of your purchases and create a journal entry that credits the settlement account and debits the liability account - saving you hours of manual reconciliation work at the end of your statement period. - -_“Moving from our other bank and getting Expensify cards into the hands of employees was super easy. I also love how simple it is to adjust credit limits and the auto reconciliation with the daily settlement.”_ -_- Robin Gresham, Senior Accounting Systems Manager at SunCommon_ - -## Step 10: Set up Bill Pay and Invoicing -As a VC-backed startup, you might have vendors you work with that send you bills. And in most cases, you probably use some third party to pay those bills if you aren’t cutting checks the old fashioned way. Similarly, you probably have clients you invoice from time to time. As an all-in-one solution, we’re here to make bill payments and invoicing easy, and every policy and workspace comes with bill pay and invoicing - at no additional cost. Since you have your business bank account verified, you can either pay your bills via ACH. Alternatively, you can pay via credit card or by check. - -Let’s first chat through how Bill Pay works - -1. Have your vendors submit bills to domain.com@expensify.cash. - - This email address comes with every account, so no need to activate it anywhere. -2. Once the bill has been received, we’ll create the bill for your review directly in Expensify -3. At the top of the bill/invoice, you’ll notice a Pay button. Once you click that, you’ll see options including ACH, credit/debit card, along with mailing a physical check. - -Similarly, you can send bills directly from Expensify as well. - -1. From the *Reports* tab, select the down arrow next to *New Report* and select *Bill* -2. Next, enter in the Supplier’s email address, the Merchant name, total amount and date -3. At this point, you can also enter in an attachment to further validate the bill if necessary -4. Click *Submit*, we’ll forward the newly created bill directly to your Supplier. - -Reports, invoices and bills - they are largely the same in theory, just with different rules. As such, creating an invoice is just like creating an expense report and even a Bill. - -1. From the *Reports* tab, select the down arrow next to *New Report* and select *Invoice*. -2. Add all of the expenses/transactions tied to the Invoice -3. Enter the recipient’s email address, a memo if needed, and a due date for when it needs to get paid, and click *Send* - -You’ll notice it’s a slightly different flow from creating a Bill. Here, you are adding the transactions tied to the Invoice, and establishing a due date for when it needs to get paid. If you need to apply any markups, you can do so from your policy settings under the Invoices tab. - -## Step 11: Add a billing card -Now that we’ve gone through all of the steps for setting up your account, let’s make it official so there are no interruptions in service as your employees begin using Expensify. We handle billing via a billing card, and to add one: - -1. Go to *Account > Settings > Payments* -2. Select *Add Payment Card* -3. Enter your name, card number, postal code, expiration and CVV -4. Click *Accept Terms* - -# You’re all set! -Congrats, you are all set up! If you need any assistance with anything mentioned above, reach out to either your Setup Specialist or your Account Manager directly in *[new.expensify.com](https://new.expensify.com)*. Don’t have one yet? Create a Control Policy, and we’ll automatically assign a dedicated Setup Specialist to you. From fcb7bd0d0bca3a7fd5411031f6ec8cd7a2582f3a Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 20 Sep 2023 15:51:28 +0530 Subject: [PATCH 086/151] delete existing articles --- .../request-money/Request-and-Split-Bills.md | 38 ------------------- 1 file changed, 38 deletions(-) delete mode 100644 docs/articles/request-money/Request-and-Split-Bills.md diff --git a/docs/articles/request-money/Request-and-Split-Bills.md b/docs/articles/request-money/Request-and-Split-Bills.md deleted file mode 100644 index bb27cd75c742..000000000000 --- a/docs/articles/request-money/Request-and-Split-Bills.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -title: Request Money and Split Bills with Friends -description: Everything you need to know about Requesting Money and Splitting Bills with Friends! ---- - - - -# How do these Payment Features work? -Our suite of money movement features enables you to request money owed by an individual or split a bill with a group. - -**Request Money** lets your friends pay you back directly in Expensify. When you send a payment request to a friend, Expensify will display the amount owed and the option to pay the corresponding request in a chat between you. - -**Split Bill** allows you to split payments between friends and ensures the person who settled the tab gets paid back. - -These two features ensure you can live in the moment and settle up afterward. - -# How to Request Money -- Select the Green **+** button and choose **Request Money** -- Select the relevant option: - - **Manual:** Enter the merchant and amount manually. - - **Scan:** Take a photo of the receipt to have the merchant and amount auto-filled. - - **Distance:** Enter the details of your trip, plus any stops along the way, and the mileage and amount will be automatically calculated. -- Search for the user or enter their email! -- Enter a reason for the request (optional) -- Click **Request!** -- If you change your mind, all you have to do is click **Cancel** -- The user will be able to **Settle up outside of Expensify** or pay you via **Venmo** or **PayPal.me** - -# How to Split a Bill -- Select the Green **+** button and choose **Split Bill** -- Enter the total amount for the bill and click **Next** -- Search for users or enter their emails and **Select** -- Enter a reason for the split -- The split is then shared equally between the attendees - -# FAQs -## Can I request money from more than one person at a time? -If you need to request money for more than one person at a time, you’ll want to use the Split Bill feature. The Request Money option is for one-to-one payments between two people. From 97d4b249dcb2ffc5abd730ffd03688074015bd2e Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 20 Sep 2023 15:52:18 +0530 Subject: [PATCH 087/151] delete existing articles --- .../paying-friends/Request-and-Split-Bills.md | 35 ----------- .../split-bills/workspaces/The-Free-Plan.md | 62 ------------------- 2 files changed, 97 deletions(-) delete mode 100644 docs/articles/split-bills/paying-friends/Request-and-Split-Bills.md delete mode 100644 docs/articles/split-bills/workspaces/The-Free-Plan.md diff --git a/docs/articles/split-bills/paying-friends/Request-and-Split-Bills.md b/docs/articles/split-bills/paying-friends/Request-and-Split-Bills.md deleted file mode 100644 index a2c63cf6f8f7..000000000000 --- a/docs/articles/split-bills/paying-friends/Request-and-Split-Bills.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -title: Request Money and Split Bills with Friends -description: Everything you need to know about Requesting Money and Splitting Bills with Friends! ---- - - - -# How do these Payment Features work? -Our suite of money movement features enables you to request money owed by an individual or split a bill with a group. - -**Request Money** lets your friends pay you back directly in Expensify. When you send a payment request to a friend, Expensify will display the amount owed and the option to pay the corresponding request in a chat between you. - -**Split Bill** allows you to split payments between friends and ensures the person who settled the tab gets paid back. - -These two features ensure you can live in the moment and settle up afterward. - -# How to Request Money -- Select the Green **+** button and choose **Request Money** -- Enter the amount **$** they owe and click **Next** -- Search for the user or enter their email! -- Enter a reason for the request (optional) -- Click **Request!** -- If you change your mind, all you have to do is click **Cancel** -- The user will be able to **Settle up outside of Expensify** or pay you via **Venmo** or **PayPal.me** - -# How to Split a Bill -- Select the Green **+** button and choose **Split Bill** -- Enter the total amount for the bill and click **Next** -- Search for users or enter their emails and **Select** -- Enter a reason for the split -- The split is then shared equally between the attendees - -# FAQs -## Can I request money from more than one person at a time? -If you need to request money for more than one person at a time, you’ll want to use the Split Bill feature. The Request Money option is for one-to-one payments between two people. diff --git a/docs/articles/split-bills/workspaces/The-Free-Plan.md b/docs/articles/split-bills/workspaces/The-Free-Plan.md deleted file mode 100644 index 45c9d09d4777..000000000000 --- a/docs/articles/split-bills/workspaces/The-Free-Plan.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -title: The Free Plan -description: Everything you need to know about Expensify's Free Plan! ---- - - - -# What is the Free Plan? -The free plan is ideal for start-ups and small businesses to manage expenses. With the Free Plan, a workspace admin can set their team up with Expensify Cards, reimburse cash expenses, send invoices, and manage bills, all for free! You will have total visibility and control over all spending associated with your workspace in real time. - -# Features Included with the Free Plan -- Expensify Cards for all employees -- Invoicing -- Bill Pay -- Unlimited receipt scanning for everyone in the company -- Free next-day ACH reimbursements for cash expenses -- Up to 4% [cash back](https://community.expensify.com/discussion/8454/4-cash-back-on-the-expensify-card-is-here-to-stay) -- Free corporate travel booking - -# Setting Up the Free Plan -- Navigate to new.expensify.com, enter your company email address, and set a password -- Click the **green “+”** button and select **_New workspace_** - -Once you’ve created your Workspace, you will receive a message from Concierge encouraging you to chat with your Setup Specialist. Click the link in the message, and your designated Setup Specialist will guide you through how to configure your company setup. - -Once you’ve completed your company setup, you should have completed the following tasks: - -- Connected a business bank account (Settings menu > Click **_Bank account_** and follow the prompts). -- Invited members to the workspace -- Assigned Expensify Cards - -# Inviting Members to the Free Plan: -- Navigate to the Settings Menu and click **_Members_** to invite your team. You can invite employees one at a time, or you can invite multiple users by listing out their email addresses separated by a comma -- To use the Expensify Card, you must invite them to your workspace via your company email address (i.e., admin@companyemail.com and NOT admin@gmail.com). - -# Managing the Free Plan -To access your workspace settings, click your profile icon and then on your workspace name. - -This settings menu allows you to manage your workspace members, issue additional Expensify Cards, and utilize this plan’s various bill pay and payment options. - -# FAQs -## Do I need a business bank account to use the Free Plan? - -You will need a US business checking account if you want to enable the Expensify Card and set up direct ACH reimbursement. -You will need to take a few steps to verify your business bank account and connect it to Expensify. You will also need to set aside some time and have your ID ready. -If you're not in the US, you can still use the Free Plan, but the Expensify Card and direct reimbursement will not be available. - -## Can my workspace have more than one Admin? - -The Expensify Workplace only allows for one admin (the workspace creator). - -## Scheduled Submit is set to weekly on the Free Plan. Can I change this? - -No, expense reports on the Free Plan submit weekly, and there is no way to customize approval settings. - -## With the Free Plan, can I add my own categories? - -Categories are standardized on the Free Plan and can’t be edited. Custom categories and tags or accounting system integration are available on a paid plan. - -## With the Free Plan, can I export reports using a custom format? - -The Free Plan offers standard report export formats. You'll need to upgrade to a paid plan to create a custom export format. From 575948c4425126673d1115dc1b0068ca1f7846f2 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 20 Sep 2023 16:41:34 +0530 Subject: [PATCH 088/151] add routes for new hubs and platforms --- docs/_data/_routes.yml | 140 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 139 insertions(+), 1 deletion(-) diff --git a/docs/_data/_routes.yml b/docs/_data/_routes.yml index 85593156a4b1..20582b6b8c7e 100644 --- a/docs/_data/_routes.yml +++ b/docs/_data/_routes.yml @@ -1,4 +1,142 @@ home: href: home title: Welcome to ExpensifyHelp! - description: Find the answers to all of your questions about receipts, expenses, corporate cards, or anything else in the spend management universe. + description: Questions? Find the answers by clicking a Category or using the search bar located in the left-hand menu. + +platforms: + - href: expensify-classic + title: Expensify Classic + hub-title: Expensify Classic - Help & Resources + url: expensify.com + description: Your account settings will look something like this + image: /assets/images/paper-airplane.svg + + # Hubs are comprised of sections and articles. Sections contain multiple related articles, but there can be standalone articles as well + hubs: + - href: account-settings + title: Account Settings + icon: /assets/images/gears.svg + description: With only a couple of clicks, split bills with your friends or coworkers. + + - href: bank-accounts-and-credit-cards + title: Bank Accounts & Credit Cards + icon: /assets/images/bank-card.svg + description: Request money for work expenses, bills, or a night out with friends. + + - href: billing-and-subscriptions + title: Billing & Subscriptions + icon: /assets/images/money-wings.svg + description: Best practices for how to best deploy Expensify for your business + + - href: expense-and-report-features + title: Expense & Report Features + icon: /assets/images/money-receipt.svg + description: Everything else you're looking for is right here. + + - href: expensify-card + title: Expensify Card + icon: /assets/images/hand-card.svg + description: Request money for work expenses, bills, or a night out with friends. + + - href: exports + title: Exports + icon: /assets/images/monitor.svg + description: Best practices for how to best deploy Expensify for your business + + - href: get-paid-back + title: Get Paid Back + description: Everything else you're looking for is right here. + icon: /assets/images/money-into-wallet.svg + + - href: getting-started + title: Getting Started + description: Everything else you're looking for is right here. + icon: /assets/images/accounting.svg + + - href: integrations + title: Integrations + description: Everything else you're looking for is right here. + icon: /assets/images/workflow.svg + + - href: manage-employees-and-report-approvals + title: Manage Employees & Report Approvals + icon: /assets/images/envelope-receipt.svg + description: Everything else you're looking for is right here. + + - href: policy-and-domain-settings + title: Policy & Domain Setting + icon: /assets/images/shield.svg + description: Everything else you're looking for is right here. + + - href: send-payments + title: Send Payments + icon: /assets/images/money-wings.svg + description: Everything else you're looking for is right here. + + - href: new-expensify + title: New Expensify + hub-title: New Expensify - Help & Resources + url: new.expensify.com + description: Your account settings will look something like this + image: /assets/images/paper-airplane.svg + + hubs: + - href: account-settings + title: Account Settings + icon: /assets/images/gears.svg + description: With only a couple of clicks, split bills with your friends or coworkers. + + - href: bank-accounts-and-credit-cards + title: Bank Accounts & Credit Cards + icon: /assets/images/bank-card.svg + description: description + + - href: billing-and-plan-types + title: Billing & Plan Types + icon: /assets/images/money-wings.svg + description: description + + - href: expense-and-report-features + title: Expense & Report Features + icon: /assets/images/money-receipt.svg + description: description + + - href: expensify-card + title: Expensify Card + icon: /assets/images/hand-card.svg + description: description + + - href: exports + title: Exports + icon: /assets/images/monitor.svg + description: description + + - href: get-paid-back + title: Get Paid Back + icon: /assets/images/money-into-wallet.svg + description: description + + - href: getting-started + title: Getting Started + icon: /assets/images/accounting.svg + description: description + + - href: integrations + title: Integrations + icon: /assets/images/workflow.svg + description: description + + - href: manage-employees-and-report-approvals + title: Manage Employees & Report Approvals + icon: /assets/images/envelope-receipt.svg + description: description + + - href: send-payments + title: Send Payments + icon: /assets/images/money-wings.svg + description: description. + + - href: workspace-and-domain-settings + title: Workspace & Domain Settings + icon: /assets/images/shield.svg + description: description. From 5cdc7fb71431e3d592999d49efba4e9beb43e462 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 20 Sep 2023 14:59:46 +0200 Subject: [PATCH 089/151] clarify comment --- src/libs/ReportUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 553cb0243f52..ed2e85a4ff11 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1649,7 +1649,7 @@ function getRootParentReport(report) { return {}; } - // Returns the founded root report, because it does not have a parentReportID + // Returns the current report as the root report, because it does not have a parentReportID if (!report.parentReportID) { return report; } From ce2907d21401cf6c713940e209d8539316e642f6 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 20 Sep 2023 15:06:46 +0200 Subject: [PATCH 090/151] perform conditions --- src/components/ReportActionItem/MoneyRequestView.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index d8ec67ae59da..178cab75a0c2 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -1,8 +1,8 @@ import React, {useMemo} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; import lodashGet from 'lodash/get'; +import lodashValues from 'lodash/values'; import PropTypes from 'prop-types'; import reportPropTypes from '../../pages/reportPropTypes'; import ONYXKEYS from '../../ONYXKEYS'; @@ -90,8 +90,7 @@ function MoneyRequestView({betas, report, parentReport, policyCategories, should // A flag for verifying that the current report is a sub-report of a workspace chat const isPolicyExpenseChat = useMemo(() => ReportUtils.isPolicyExpenseChat(ReportUtils.getRootParentReport(report)), [report]); // A flag for showing categories - const shouldShowCategory = - isPolicyExpenseChat && Permissions.canUseCategories(betas) && (!_.isEmpty(transactionCategory) || OptionsListUtils.hasEnabledOptions(_.values(policyCategories))); + const shouldShowCategory = isPolicyExpenseChat && Permissions.canUseCategories(betas) && (transactionCategory || OptionsListUtils.hasEnabledOptions(lodashValues(policyCategories))); let description = `${translate('iou.amount')} • ${translate('iou.cash')}`; if (isSettled) { From bd716a534ba9a5a1eb3446b44c619899449ec1a8 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Wed, 20 Sep 2023 15:52:29 +0200 Subject: [PATCH 091/151] Fix prettier --- src/libs/migrateOnyx.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/libs/migrateOnyx.js b/src/libs/migrateOnyx.js index 3e92bb17c842..8ade54c0b5d2 100644 --- a/src/libs/migrateOnyx.js +++ b/src/libs/migrateOnyx.js @@ -13,14 +13,7 @@ export default function () { return new Promise((resolve) => { // Add all migrations to an array so they are executed in order - const migrationPromises = [ - RenameActiveClientsKey, - RenamePriorityModeKey, - AddEncryptedAuthToken, - RenameExpensifyNewsStatus, - AddLastVisibleActionCreated, - PersonalDetailsByAccountID, - ]; + const migrationPromises = [RenameActiveClientsKey, RenamePriorityModeKey, AddEncryptedAuthToken, RenameExpensifyNewsStatus, AddLastVisibleActionCreated, PersonalDetailsByAccountID]; // Reduce all promises down to a single promise. All promises run in a linear fashion, waiting for the // previous promise to finish before moving onto the next one. From 63fa6e289218f6bb43d83fe081160cd54550b504 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 20 Sep 2023 20:01:08 +0530 Subject: [PATCH 092/151] add platforms and hubs to routes using script --- .github/scripts/createDocsRoutes.js | 50 +++++++++++++++++++---------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/.github/scripts/createDocsRoutes.js b/.github/scripts/createDocsRoutes.js index 0fc9aa33ff27..2049aed7247a 100644 --- a/.github/scripts/createDocsRoutes.js +++ b/.github/scripts/createDocsRoutes.js @@ -2,7 +2,7 @@ const yaml = require('js-yaml'); const fs = require('fs'); const _ = require('underscore'); -const warn = 'Number of hubs in _routes.yml does not match number of hubs in docs/articles. Please update _routes.yml with hub info.'; +const getNumberOfRoutesNotMatchingError = (platform) => `Number of hubs in _routes.yml does not match number of hubs in docs/${platform}/articles. Please update _routes.yml with hub info.`; const disclaimer = '# This file is auto-generated. Do not edit it directly. Use npm run createDocsRoutes instead.\n'; const docsDir = `${process.cwd()}/docs`; const routes = yaml.load(fs.readFileSync(`${docsDir}/_data/_routes.yml`, 'utf8')); @@ -28,7 +28,7 @@ function getArticleObj(filename) { } /** - * If the articlea / sections exist in the hub, then push the entry to the array. + * If the article / sections exist in the hub, then push the entry to the array. * Otherwise, create the array and push the entry to it. * @param {*} hubs - The hubs array * @param {*} hub - The hub we are iterating @@ -45,19 +45,40 @@ function pushOrCreateEntry(hubs, hub, key, entry) { } function run() { - const hubs = fs.readdirSync(`${docsDir}/articles`); - if (hubs.length !== routes.hubs.length) { - // If new hubs have been added without metadata addition to _routes.yml - console.error(warn); - process.exit(1); + const newExpensifyHubs = fs.readdirSync(`${docsDir}/articles/new-expensify`); + const expensifyClassicHubs = fs.readdirSync(`${docsDir}/articles/expensify-classic`); + + const newExpensifyRoute = routes.platforms.find((platform) => platform.href === "new-expensify"); + const expensifyClassicRoute = routes.platforms.find((platform) => platform.href === "expensify-classic"); + + if (newExpensifyHubs.length !== newExpensifyRoute.hubs.length) { + console.error(getNumberOfRoutesNotMatchingError("new-expensify")); + return 1; } + + if (expensifyClassicHubs.length !== expensifyClassicRoute.hubs.length) { + console.error(getNumberOfRoutesNotMatchingError("expensify-classic")); + return 1; + } + + createHubsWithArticles(expensifyClassicHubs, "expensify-classic", expensifyClassicRoute.hubs); + createHubsWithArticles(newExpensifyHubs, "new-expensify", newExpensifyRoute.hubs); + + // Convert the object to YAML and write it to the file + let yamlString = yaml.dump(routes); + yamlString = disclaimer + yamlString; + fs.writeFileSync(`${docsDir}/_data/routes.yml`, yamlString); +} + + +function createHubsWithArticles(hubs, platformName, routeHubs) { _.each(hubs, (hub) => { // Iterate through each directory in articles - fs.readdirSync(`${docsDir}/articles/${hub}`).forEach((fileOrFolder) => { + fs.readdirSync(`${docsDir}/articles/${platformName}/${hub}`).forEach((fileOrFolder) => { // If the directory content is a markdown file, then it is an article if (fileOrFolder.endsWith('.md')) { const articleObj = getArticleObj(fileOrFolder); - pushOrCreateEntry(routes.hubs, hub, 'articles', articleObj); + pushOrCreateEntry(routeHubs, hub, 'articles', articleObj); return; } @@ -66,27 +87,22 @@ function run() { const articles = []; // Each subfolder will be a section containing articles - fs.readdirSync(`${docsDir}/articles/${hub}/${section}`).forEach((subArticle) => { + fs.readdirSync(`${docsDir}/articles/${platformName}/${hub}/${section}`).forEach((subArticle) => { articles.push(getArticleObj(subArticle)); }); - pushOrCreateEntry(routes.hubs, hub, 'sections', { + pushOrCreateEntry(routeHubs, hub, 'sections', { href: section, title: toTitleCase(section.replaceAll('-', ' ')), articles, }); }); }); - - // Convert the object to YAML and write it to the file - let yamlString = yaml.dump(routes); - yamlString = disclaimer + yamlString; - fs.writeFileSync(`${docsDir}/_data/routes.yml`, yamlString); } try { run(); } catch (error) { console.error('A problem occurred while trying to read the directories.', error); - process.exit(1); + return 1; } From aa6972bb5f81c89d3ab2949863ea83ea6e6e3d97 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 20 Sep 2023 20:01:56 +0530 Subject: [PATCH 093/151] change variablename --- .github/scripts/createDocsRoutes.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/scripts/createDocsRoutes.js b/.github/scripts/createDocsRoutes.js index 2049aed7247a..76349bab1cc6 100644 --- a/.github/scripts/createDocsRoutes.js +++ b/.github/scripts/createDocsRoutes.js @@ -2,7 +2,7 @@ const yaml = require('js-yaml'); const fs = require('fs'); const _ = require('underscore'); -const getNumberOfRoutesNotMatchingError = (platform) => `Number of hubs in _routes.yml does not match number of hubs in docs/${platform}/articles. Please update _routes.yml with hub info.`; +const warn = (platform) => `Number of hubs in _routes.yml does not match number of hubs in docs/${platform}/articles. Please update _routes.yml with hub info.`; const disclaimer = '# This file is auto-generated. Do not edit it directly. Use npm run createDocsRoutes instead.\n'; const docsDir = `${process.cwd()}/docs`; const routes = yaml.load(fs.readFileSync(`${docsDir}/_data/_routes.yml`, 'utf8')); @@ -52,12 +52,12 @@ function run() { const expensifyClassicRoute = routes.platforms.find((platform) => platform.href === "expensify-classic"); if (newExpensifyHubs.length !== newExpensifyRoute.hubs.length) { - console.error(getNumberOfRoutesNotMatchingError("new-expensify")); + console.error(warn("new-expensify")); return 1; } if (expensifyClassicHubs.length !== expensifyClassicRoute.hubs.length) { - console.error(getNumberOfRoutesNotMatchingError("expensify-classic")); + console.error(warn("expensify-classic")); return 1; } From 2309991ba566ac20d91316d7530e0538cecb379e Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 20 Sep 2023 20:02:15 +0530 Subject: [PATCH 094/151] refactor --- .github/scripts/createDocsRoutes.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/scripts/createDocsRoutes.js b/.github/scripts/createDocsRoutes.js index 76349bab1cc6..ff3c80f3e402 100644 --- a/.github/scripts/createDocsRoutes.js +++ b/.github/scripts/createDocsRoutes.js @@ -51,13 +51,13 @@ function run() { const newExpensifyRoute = routes.platforms.find((platform) => platform.href === "new-expensify"); const expensifyClassicRoute = routes.platforms.find((platform) => platform.href === "expensify-classic"); - if (newExpensifyHubs.length !== newExpensifyRoute.hubs.length) { - console.error(warn("new-expensify")); + if (expensifyClassicHubs.length !== expensifyClassicRoute.hubs.length) { + console.error(warn("expensify-classic")); return 1; } - if (expensifyClassicHubs.length !== expensifyClassicRoute.hubs.length) { - console.error(warn("expensify-classic")); + if (newExpensifyHubs.length !== newExpensifyRoute.hubs.length) { + console.error(warn("new-expensify")); return 1; } From fe063d51b354a465fb6d3b5e69785cd511f2a657 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 20 Sep 2023 20:18:32 +0530 Subject: [PATCH 095/151] fix: improve readability --- .github/scripts/createDocsRoutes.js | 31 +++++++++++++++++++---------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/.github/scripts/createDocsRoutes.js b/.github/scripts/createDocsRoutes.js index ff3c80f3e402..d74b00172fcf 100644 --- a/.github/scripts/createDocsRoutes.js +++ b/.github/scripts/createDocsRoutes.js @@ -6,6 +6,10 @@ const warn = (platform) => `Number of hubs in _routes.yml does not match number const disclaimer = '# This file is auto-generated. Do not edit it directly. Use npm run createDocsRoutes instead.\n'; const docsDir = `${process.cwd()}/docs`; const routes = yaml.load(fs.readFileSync(`${docsDir}/_data/_routes.yml`, 'utf8')); +const platformNames = { + expensifyClassic: 'expensify-classic', + newExpensify: 'new-expensify', +}; /** * @param {String} str - The string to convert to title case @@ -45,24 +49,24 @@ function pushOrCreateEntry(hubs, hub, key, entry) { } function run() { - const newExpensifyHubs = fs.readdirSync(`${docsDir}/articles/new-expensify`); - const expensifyClassicHubs = fs.readdirSync(`${docsDir}/articles/expensify-classic`); + const expensifyClassicArticleHubs = fs.readdirSync(`${docsDir}/articles/${platformNames.expensifyClassic}`); + const newExpensifyArticleHubs = fs.readdirSync(`${docsDir}/articles/${platformNames.newExpensify}`); - const newExpensifyRoute = routes.platforms.find((platform) => platform.href === "new-expensify"); - const expensifyClassicRoute = routes.platforms.find((platform) => platform.href === "expensify-classic"); + const expensifyClassicRoute = routes.platforms.find((platform) => platform.href === platformNames.expensifyClassic); + const newExpensifyRoute = routes.platforms.find((platform) => platform.href === platformNames.newExpensify); - if (expensifyClassicHubs.length !== expensifyClassicRoute.hubs.length) { - console.error(warn("expensify-classic")); + if (expensifyClassicArticleHubs.length !== expensifyClassicRoute.hubs.length) { + console.error(warn(platformNames.expensifyClassic)); return 1; } - if (newExpensifyHubs.length !== newExpensifyRoute.hubs.length) { - console.error(warn("new-expensify")); + if (newExpensifyArticleHubs.length !== newExpensifyRoute.hubs.length) { + console.error(warn(platformNames.newExpensify)); return 1; } - createHubsWithArticles(expensifyClassicHubs, "expensify-classic", expensifyClassicRoute.hubs); - createHubsWithArticles(newExpensifyHubs, "new-expensify", newExpensifyRoute.hubs); + createHubsWithArticles(expensifyClassicArticleHubs, platformNames.expensifyClassic, expensifyClassicRoute.hubs); + createHubsWithArticles(newExpensifyArticleHubs, platformNames.newExpensify, newExpensifyRoute.hubs); // Convert the object to YAML and write it to the file let yamlString = yaml.dump(routes); @@ -70,7 +74,12 @@ function run() { fs.writeFileSync(`${docsDir}/_data/routes.yml`, yamlString); } - +/** + * Add articles and sections to hubs + * @param {Array} hubs - The hubs inside docs/articles/ for a platform + * @param {String} platformName - Expensify Classic or New Expensify + * @param {Array} routeHubs - The hubs insude docs/data/_routes.yml for a platform + */ function createHubsWithArticles(hubs, platformName, routeHubs) { _.each(hubs, (hub) => { // Iterate through each directory in articles From ce58e36ca393db55cf19d553f7944aeee7fd0c5d Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 20 Sep 2023 20:21:47 +0530 Subject: [PATCH 096/151] add expensify classic hubs --- docs/expensify-classic/hubs/account-settings.html | 6 ++++++ .../hubs/bank-accounts-and-credit-cards.html | 6 ++++++ docs/expensify-classic/hubs/billing-and-subscriptions.html | 6 ++++++ .../expensify-classic/hubs/expense-and-report-features.html | 6 ++++++ docs/expensify-classic/hubs/expensify-card.html | 6 ++++++ docs/expensify-classic/hubs/exports.html | 6 ++++++ docs/expensify-classic/hubs/get-paid-back.html | 6 ++++++ docs/expensify-classic/hubs/getting-started.html | 6 ++++++ docs/expensify-classic/hubs/index.html | 6 ++++++ docs/expensify-classic/hubs/integrations.html | 6 ++++++ .../hubs/manage-employees-and-report-approvals.html | 6 ++++++ docs/expensify-classic/hubs/policy-and-domain-settings.html | 6 ++++++ docs/expensify-classic/hubs/send-payments.html | 6 ++++++ 13 files changed, 78 insertions(+) create mode 100644 docs/expensify-classic/hubs/account-settings.html create mode 100644 docs/expensify-classic/hubs/bank-accounts-and-credit-cards.html create mode 100644 docs/expensify-classic/hubs/billing-and-subscriptions.html create mode 100644 docs/expensify-classic/hubs/expense-and-report-features.html create mode 100644 docs/expensify-classic/hubs/expensify-card.html create mode 100644 docs/expensify-classic/hubs/exports.html create mode 100644 docs/expensify-classic/hubs/get-paid-back.html create mode 100644 docs/expensify-classic/hubs/getting-started.html create mode 100644 docs/expensify-classic/hubs/index.html create mode 100644 docs/expensify-classic/hubs/integrations.html create mode 100644 docs/expensify-classic/hubs/manage-employees-and-report-approvals.html create mode 100644 docs/expensify-classic/hubs/policy-and-domain-settings.html create mode 100644 docs/expensify-classic/hubs/send-payments.html diff --git a/docs/expensify-classic/hubs/account-settings.html b/docs/expensify-classic/hubs/account-settings.html new file mode 100644 index 000000000000..434761a6c4fa --- /dev/null +++ b/docs/expensify-classic/hubs/account-settings.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Account Settings +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/expensify-classic/hubs/bank-accounts-and-credit-cards.html b/docs/expensify-classic/hubs/bank-accounts-and-credit-cards.html new file mode 100644 index 000000000000..2f91f0913d6e --- /dev/null +++ b/docs/expensify-classic/hubs/bank-accounts-and-credit-cards.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Bank Accounts & Credit Cards +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/expensify-classic/hubs/billing-and-subscriptions.html b/docs/expensify-classic/hubs/billing-and-subscriptions.html new file mode 100644 index 000000000000..05dc38835b51 --- /dev/null +++ b/docs/expensify-classic/hubs/billing-and-subscriptions.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Billing & Subscriptions +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/expensify-classic/hubs/expense-and-report-features.html b/docs/expensify-classic/hubs/expense-and-report-features.html new file mode 100644 index 000000000000..44afa4b18b51 --- /dev/null +++ b/docs/expensify-classic/hubs/expense-and-report-features.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Expense & Report Settings +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/expensify-classic/hubs/expensify-card.html b/docs/expensify-classic/hubs/expensify-card.html new file mode 100644 index 000000000000..3afd8ac662e7 --- /dev/null +++ b/docs/expensify-classic/hubs/expensify-card.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Expensify Card +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/expensify-classic/hubs/exports.html b/docs/expensify-classic/hubs/exports.html new file mode 100644 index 000000000000..16c96cb51d01 --- /dev/null +++ b/docs/expensify-classic/hubs/exports.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Exports +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/expensify-classic/hubs/get-paid-back.html b/docs/expensify-classic/hubs/get-paid-back.html new file mode 100644 index 000000000000..1f84c1510b92 --- /dev/null +++ b/docs/expensify-classic/hubs/get-paid-back.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Get Paid Back +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/expensify-classic/hubs/getting-started.html b/docs/expensify-classic/hubs/getting-started.html new file mode 100644 index 000000000000..14ca13d0c2e8 --- /dev/null +++ b/docs/expensify-classic/hubs/getting-started.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Getting Started +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/expensify-classic/hubs/index.html b/docs/expensify-classic/hubs/index.html new file mode 100644 index 000000000000..05c7b52bfa2d --- /dev/null +++ b/docs/expensify-classic/hubs/index.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Expensify Classic +--- + +{% include platform.html %} \ No newline at end of file diff --git a/docs/expensify-classic/hubs/integrations.html b/docs/expensify-classic/hubs/integrations.html new file mode 100644 index 000000000000..d1f173534c8a --- /dev/null +++ b/docs/expensify-classic/hubs/integrations.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Integrations +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/expensify-classic/hubs/manage-employees-and-report-approvals.html b/docs/expensify-classic/hubs/manage-employees-and-report-approvals.html new file mode 100644 index 000000000000..788e445ebc91 --- /dev/null +++ b/docs/expensify-classic/hubs/manage-employees-and-report-approvals.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Manage Employees And Report Approvals +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/expensify-classic/hubs/policy-and-domain-settings.html b/docs/expensify-classic/hubs/policy-and-domain-settings.html new file mode 100644 index 000000000000..ffd514fcb6fa --- /dev/null +++ b/docs/expensify-classic/hubs/policy-and-domain-settings.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Policy And Domain Settings +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/expensify-classic/hubs/send-payments.html b/docs/expensify-classic/hubs/send-payments.html new file mode 100644 index 000000000000..c8275af5c353 --- /dev/null +++ b/docs/expensify-classic/hubs/send-payments.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Send Payments +--- + +{% include hub.html %} \ No newline at end of file From 2a3e8ea52f01dfa49d1c7392cc5be424a5dee69f Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 20 Sep 2023 20:22:05 +0530 Subject: [PATCH 097/151] add new expensify hubs --- docs/new-expensify/hubs/account-settings.html | 6 ++++++ docs/new-expensify/hubs/bank-accounts-and-credit-cards.html | 6 ++++++ docs/new-expensify/hubs/billing-and-plan-types.html | 6 ++++++ docs/new-expensify/hubs/expense-and-report-features.html | 6 ++++++ docs/new-expensify/hubs/expensify-card.html | 6 ++++++ docs/new-expensify/hubs/exports.html | 0 docs/new-expensify/hubs/get-paid-back.html | 6 ++++++ docs/new-expensify/hubs/getting-started.html | 6 ++++++ docs/new-expensify/hubs/index.html | 6 ++++++ docs/new-expensify/hubs/integrations.html | 6 ++++++ .../hubs/manage-employees-and-report-approvals.html | 6 ++++++ docs/new-expensify/hubs/send-payments.html | 6 ++++++ docs/new-expensify/hubs/workspace-and-domain-settings.html | 6 ++++++ 13 files changed, 72 insertions(+) create mode 100644 docs/new-expensify/hubs/account-settings.html create mode 100644 docs/new-expensify/hubs/bank-accounts-and-credit-cards.html create mode 100644 docs/new-expensify/hubs/billing-and-plan-types.html create mode 100644 docs/new-expensify/hubs/expense-and-report-features.html create mode 100644 docs/new-expensify/hubs/expensify-card.html create mode 100644 docs/new-expensify/hubs/exports.html create mode 100644 docs/new-expensify/hubs/get-paid-back.html create mode 100644 docs/new-expensify/hubs/getting-started.html create mode 100644 docs/new-expensify/hubs/index.html create mode 100644 docs/new-expensify/hubs/integrations.html create mode 100644 docs/new-expensify/hubs/manage-employees-and-report-approvals.html create mode 100644 docs/new-expensify/hubs/send-payments.html create mode 100644 docs/new-expensify/hubs/workspace-and-domain-settings.html diff --git a/docs/new-expensify/hubs/account-settings.html b/docs/new-expensify/hubs/account-settings.html new file mode 100644 index 000000000000..434761a6c4fa --- /dev/null +++ b/docs/new-expensify/hubs/account-settings.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Account Settings +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/new-expensify/hubs/bank-accounts-and-credit-cards.html b/docs/new-expensify/hubs/bank-accounts-and-credit-cards.html new file mode 100644 index 000000000000..2f91f0913d6e --- /dev/null +++ b/docs/new-expensify/hubs/bank-accounts-and-credit-cards.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Bank Accounts & Credit Cards +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/new-expensify/hubs/billing-and-plan-types.html b/docs/new-expensify/hubs/billing-and-plan-types.html new file mode 100644 index 000000000000..b49b2b62b1d6 --- /dev/null +++ b/docs/new-expensify/hubs/billing-and-plan-types.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Billing & Plan Types +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/new-expensify/hubs/expense-and-report-features.html b/docs/new-expensify/hubs/expense-and-report-features.html new file mode 100644 index 000000000000..0057ae0fa46c --- /dev/null +++ b/docs/new-expensify/hubs/expense-and-report-features.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Expense and Report Features +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/new-expensify/hubs/expensify-card.html b/docs/new-expensify/hubs/expensify-card.html new file mode 100644 index 000000000000..3afd8ac662e7 --- /dev/null +++ b/docs/new-expensify/hubs/expensify-card.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Expensify Card +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/new-expensify/hubs/exports.html b/docs/new-expensify/hubs/exports.html new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/docs/new-expensify/hubs/get-paid-back.html b/docs/new-expensify/hubs/get-paid-back.html new file mode 100644 index 000000000000..1f84c1510b92 --- /dev/null +++ b/docs/new-expensify/hubs/get-paid-back.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Get Paid Back +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/new-expensify/hubs/getting-started.html b/docs/new-expensify/hubs/getting-started.html new file mode 100644 index 000000000000..14ca13d0c2e8 --- /dev/null +++ b/docs/new-expensify/hubs/getting-started.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Getting Started +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/new-expensify/hubs/index.html b/docs/new-expensify/hubs/index.html new file mode 100644 index 000000000000..de046b16e755 --- /dev/null +++ b/docs/new-expensify/hubs/index.html @@ -0,0 +1,6 @@ +--- +layout: default +title: New Expensify +--- + +{% include platform.html %} \ No newline at end of file diff --git a/docs/new-expensify/hubs/integrations.html b/docs/new-expensify/hubs/integrations.html new file mode 100644 index 000000000000..d1f173534c8a --- /dev/null +++ b/docs/new-expensify/hubs/integrations.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Integrations +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/new-expensify/hubs/manage-employees-and-report-approvals.html b/docs/new-expensify/hubs/manage-employees-and-report-approvals.html new file mode 100644 index 000000000000..31e992f32d5d --- /dev/null +++ b/docs/new-expensify/hubs/manage-employees-and-report-approvals.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Manage Employees & Report Approvals +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/new-expensify/hubs/send-payments.html b/docs/new-expensify/hubs/send-payments.html new file mode 100644 index 000000000000..c8275af5c353 --- /dev/null +++ b/docs/new-expensify/hubs/send-payments.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Send Payments +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/new-expensify/hubs/workspace-and-domain-settings.html b/docs/new-expensify/hubs/workspace-and-domain-settings.html new file mode 100644 index 000000000000..c4e5d06d6b8a --- /dev/null +++ b/docs/new-expensify/hubs/workspace-and-domain-settings.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Workspace & Domain Settings +--- + +{% include hub.html %} \ No newline at end of file From 85d8a0bfa8c7d4d288389dfca3665a1f3259ee14 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 20 Sep 2023 20:23:06 +0530 Subject: [PATCH 098/151] add expensify classic articles --- .../account-settings/Account-Access.md | 5 + .../account-settings/Close-Account.md | 5 + .../account-settings/Merge-Accounts.md | 5 + .../account-settings/Preferences.md | 5 + .../account-settings/Profile-Settings.md | 5 + .../Business-Bank-Accounts-AUS.md | 5 + .../Business-Bank-Accounts-USD.md | 5 + .../Deposit-Accounts-AUS.md | 5 + .../Deposit-Accounts-USD.md | 5 + .../Global-Reimbursement.md | 5 + .../Personal-Credit-Cards.md | 5 + .../company-cards/ANZ.md | 6 + .../company-cards/Brex.md | 5 + .../company-cards/CSV-Import.md | 5 + .../company-cards/Commercial-Card-Feeds.md | 5 + .../company-cards/Connect-Company-Cards.md | 5 + .../company-cards/Direct-Bank-Connections.md | 5 + .../company-cards/Export-To-GL-Accounts.md | 5 + .../company-cards/Reconciliation.md | 5 + .../company-cards/Troubleshooting.md | 5 + .../Annual-Subscription.md | 5 + .../Billing-Owner.md | 5 + .../Change-Plan-Or-Subscription.md | 5 + .../Consolidated-Domain-Billing.md | 5 + .../billing-and-subscriptions/Free-Trial.md | 5 + .../Individual-Subscription.md | 5 + .../billing-and-subscriptions/Overview.md | 5 + .../Pay-Per-Use-Subscription.md | 5 + .../billing-and-subscriptions/Payment-Card.md | 5 + .../billing-and-subscriptions/Tax-Exempt.md | 5 + .../Attendee-Tracking.md | 5 + .../expense-and-report-features/Currency.md | 5 + .../Expense-Rules.md | 5 + .../Expense-Types.md | 5 + .../Report-Comments.md | 5 + .../The-Expenses-Page.md | 5 + .../The-Reports-Page.md | 5 + .../expensify-card/Auto-Reconciliation.md | 5 + .../expensify-card/CPA-Card.md | 5 + .../expensify-card/Card-Settings.md | 5 + .../Connect-To-Indirect-Integration.md | 5 + .../expensify-card/File-A-Dispute.md | 5 + .../expensify-card/Get-The-Card.md | 5 + .../expensify-card/Statements.md | 5 + .../expensify-card/The-Reports-Page.md | 5 + .../exports/Custom-Templates.md | 5 + .../exports/Default-Export-Templates.md | 5 + .../expensify-classic/exports/Insights.md | 100 +++++++ .../exports/The-Reports-Page.md | 5 + .../get-paid-back/Mileage.md | 5 + .../get-paid-back/Per-Diem.md | 5 + .../get-paid-back/Third-Party-Payments.md | 5 + .../expensify-classic/get-paid-back/Trips.md | 5 + .../get-paid-back/expenses/Apply-Tax.md | 5 + .../get-paid-back/expenses/Create-Expenses.md | 5 + .../get-paid-back/expenses/Merge-Expenses.md | 5 + .../get-paid-back/expenses/Upload-Receipts.md | 5 + .../get-paid-back/reports/Create-A-Report.md | 5 + .../get-paid-back/reports/Reimbursements.md | 5 + .../getting-started/Best-Practices.md | 5 + .../getting-started/Employees.md | 5 + .../getting-started/Individual-Users.md | 5 + .../getting-started/Invite-Employees.md | 5 + .../getting-started/Plan-Types.md | 5 + .../getting-started/Policy-Admins.md | 5 + .../getting-started/Referral-Program.md | 53 ++++ .../getting-started/Security.md | 5 + .../Support/Your-Expensify-Account-Manager.md | 36 +++ .../getting-started/Using-The-App.md | 5 + ...e-Share-For-Expensify-Approved-Partners.md | 16 + .../Your-Expensify-Partner-Manager.md | 34 +++ ...ok-For-Small-To-Medium-Sized-Businesses.md | 283 ++++++++++++++++++ ...book-For-US-Based-Bootstrapped-Startups.md | 90 ++++++ ...laybook-For-US-Based-VC-Backed-Startups.md | 208 +++++++++++++ .../getting-started/tips-and-tricks.md | 5 + .../Enable-Location-Access-On-Web.md | 55 ++++ .../accounting-integrations/Bill-dot-com.md | 5 + .../accounting-integrations/FinancalForce.md | 5 + .../accounting-integrations/NetSuite.md | 5 + .../QuickBooks-Desktop.md | 5 + .../QuickBooks-Online.md | 5 + .../accounting-integrations/Sage-Intacct.md | 5 + .../accounting-integrations/Xero.md | 5 + .../integrations/hr-integrations/ADP.md | 5 + .../hr-integrations/Greenhouse.md | 5 + .../integrations/hr-integrations/Gusto.md | 5 + .../hr-integrations/QuickBooks-Time.md | 5 + .../integrations/hr-integrations/Rippling.md | 5 + .../integrations/hr-integrations/Workday.md | 5 + .../integrations/hr-integrations/Zenefits.md | 5 + .../other-integrations/Google-Apps-SSO.md | 5 + .../integrations/travel-integrations/Bolt.md | 5 + .../travel-integrations/Egencia.md | 5 + .../travel-integrations/Global-VaTax.md | 5 + .../integrations/travel-integrations/Grab.md | 5 + .../travel-integrations/Hotel-Tonight.md | 5 + .../integrations/travel-integrations/Kayak.md | 5 + .../integrations/travel-integrations/Lyft.md | 5 + .../travel-integrations/TrainLine.md | 5 + .../travel-integrations/TravelPerk.md | 5 + .../travel-integrations/Trip-Actions.md | 5 + .../travel-integrations/TripCatcher.md | 5 + .../integrations/travel-integrations/Uber.md | 5 + .../Adding-Users.md | 5 + .../Approval-Workflows.md | 5 + .../Approving-Reports.md | 5 + .../User-Roles.md | 5 + .../Vacation-Delegate.md | 8 + .../policy-and-domain-settings/Admins.md | 5 + .../policy-and-domain-settings/Categories.md | 5 + .../Domain-Admins.md | 5 + .../Domain-Members.md | 5 + .../Domains-Overview.md | 5 + .../policy-and-domain-settings/Expenses.md | 5 + .../policy-and-domain-settings/Invoicing.md | 5 + .../policy-and-domain-settings/Overview.md | 5 + .../policy-and-domain-settings/Per-Diem.md | 5 + .../Reimbursement.md | 5 + .../policy-and-domain-settings/Reports.md | 5 + .../policy-and-domain-settings/SAML.md | 5 + .../policy-and-domain-settings/Tags.md | 5 + .../policy-and-domain-settings/Tax.md | 5 + .../policy-and-domain-settings/Trips.md | 5 + .../send-payments/Pay-Bills.md | 5 + .../send-payments/Pay-Invoices.md | 5 + .../send-payments/Reimbursing-Reports.md | 5 + .../send-payments/Third-Party-Payments.md | 8 + 127 files changed, 1472 insertions(+) create mode 100644 docs/articles/expensify-classic/account-settings/Account-Access.md create mode 100644 docs/articles/expensify-classic/account-settings/Close-Account.md create mode 100644 docs/articles/expensify-classic/account-settings/Merge-Accounts.md create mode 100644 docs/articles/expensify-classic/account-settings/Preferences.md create mode 100644 docs/articles/expensify-classic/account-settings/Profile-Settings.md create mode 100644 docs/articles/expensify-classic/bank-accounts-and-credit-cards/Business-Bank-Accounts-AUS.md create mode 100644 docs/articles/expensify-classic/bank-accounts-and-credit-cards/Business-Bank-Accounts-USD.md create mode 100644 docs/articles/expensify-classic/bank-accounts-and-credit-cards/Deposit-Accounts-AUS.md create mode 100644 docs/articles/expensify-classic/bank-accounts-and-credit-cards/Deposit-Accounts-USD.md create mode 100644 docs/articles/expensify-classic/bank-accounts-and-credit-cards/Global-Reimbursement.md create mode 100644 docs/articles/expensify-classic/bank-accounts-and-credit-cards/Personal-Credit-Cards.md create mode 100644 docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/ANZ.md create mode 100644 docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Brex.md create mode 100644 docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/CSV-Import.md create mode 100644 docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Commercial-Card-Feeds.md create mode 100644 docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Connect-Company-Cards.md create mode 100644 docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Direct-Bank-Connections.md create mode 100644 docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Export-To-GL-Accounts.md create mode 100644 docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Reconciliation.md create mode 100644 docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Troubleshooting.md create mode 100644 docs/articles/expensify-classic/billing-and-subscriptions/Annual-Subscription.md create mode 100644 docs/articles/expensify-classic/billing-and-subscriptions/Billing-Owner.md create mode 100644 docs/articles/expensify-classic/billing-and-subscriptions/Change-Plan-Or-Subscription.md create mode 100644 docs/articles/expensify-classic/billing-and-subscriptions/Consolidated-Domain-Billing.md create mode 100644 docs/articles/expensify-classic/billing-and-subscriptions/Free-Trial.md create mode 100644 docs/articles/expensify-classic/billing-and-subscriptions/Individual-Subscription.md create mode 100644 docs/articles/expensify-classic/billing-and-subscriptions/Overview.md create mode 100644 docs/articles/expensify-classic/billing-and-subscriptions/Pay-Per-Use-Subscription.md create mode 100644 docs/articles/expensify-classic/billing-and-subscriptions/Payment-Card.md create mode 100644 docs/articles/expensify-classic/billing-and-subscriptions/Tax-Exempt.md create mode 100644 docs/articles/expensify-classic/expense-and-report-features/Attendee-Tracking.md create mode 100644 docs/articles/expensify-classic/expense-and-report-features/Currency.md create mode 100644 docs/articles/expensify-classic/expense-and-report-features/Expense-Rules.md create mode 100644 docs/articles/expensify-classic/expense-and-report-features/Expense-Types.md create mode 100644 docs/articles/expensify-classic/expense-and-report-features/Report-Comments.md create mode 100644 docs/articles/expensify-classic/expense-and-report-features/The-Expenses-Page.md create mode 100644 docs/articles/expensify-classic/expense-and-report-features/The-Reports-Page.md create mode 100644 docs/articles/expensify-classic/expensify-card/Auto-Reconciliation.md create mode 100644 docs/articles/expensify-classic/expensify-card/CPA-Card.md create mode 100644 docs/articles/expensify-classic/expensify-card/Card-Settings.md create mode 100644 docs/articles/expensify-classic/expensify-card/Connect-To-Indirect-Integration.md create mode 100644 docs/articles/expensify-classic/expensify-card/File-A-Dispute.md create mode 100644 docs/articles/expensify-classic/expensify-card/Get-The-Card.md create mode 100644 docs/articles/expensify-classic/expensify-card/Statements.md create mode 100644 docs/articles/expensify-classic/expensify-card/The-Reports-Page.md create mode 100644 docs/articles/expensify-classic/exports/Custom-Templates.md create mode 100644 docs/articles/expensify-classic/exports/Default-Export-Templates.md create mode 100644 docs/articles/expensify-classic/exports/Insights.md create mode 100644 docs/articles/expensify-classic/exports/The-Reports-Page.md create mode 100644 docs/articles/expensify-classic/get-paid-back/Mileage.md create mode 100644 docs/articles/expensify-classic/get-paid-back/Per-Diem.md create mode 100644 docs/articles/expensify-classic/get-paid-back/Third-Party-Payments.md create mode 100644 docs/articles/expensify-classic/get-paid-back/Trips.md create mode 100644 docs/articles/expensify-classic/get-paid-back/expenses/Apply-Tax.md create mode 100644 docs/articles/expensify-classic/get-paid-back/expenses/Create-Expenses.md create mode 100644 docs/articles/expensify-classic/get-paid-back/expenses/Merge-Expenses.md create mode 100644 docs/articles/expensify-classic/get-paid-back/expenses/Upload-Receipts.md create mode 100644 docs/articles/expensify-classic/get-paid-back/reports/Create-A-Report.md create mode 100644 docs/articles/expensify-classic/get-paid-back/reports/Reimbursements.md create mode 100644 docs/articles/expensify-classic/getting-started/Best-Practices.md create mode 100644 docs/articles/expensify-classic/getting-started/Employees.md create mode 100644 docs/articles/expensify-classic/getting-started/Individual-Users.md create mode 100644 docs/articles/expensify-classic/getting-started/Invite-Employees.md create mode 100644 docs/articles/expensify-classic/getting-started/Plan-Types.md create mode 100644 docs/articles/expensify-classic/getting-started/Policy-Admins.md create mode 100644 docs/articles/expensify-classic/getting-started/Referral-Program.md create mode 100644 docs/articles/expensify-classic/getting-started/Security.md create mode 100644 docs/articles/expensify-classic/getting-started/Support/Your-Expensify-Account-Manager.md create mode 100644 docs/articles/expensify-classic/getting-started/Using-The-App.md create mode 100644 docs/articles/expensify-classic/getting-started/approved-accountants/Card-Revenue-Share-For-Expensify-Approved-Partners.md create mode 100644 docs/articles/expensify-classic/getting-started/approved-accountants/Your-Expensify-Partner-Manager.md create mode 100644 docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md create mode 100644 docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-US-Based-Bootstrapped-Startups.md create mode 100644 docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-US-Based-VC-Backed-Startups.md create mode 100644 docs/articles/expensify-classic/getting-started/tips-and-tricks.md create mode 100644 docs/articles/expensify-classic/getting-started/tips-and-tricks/Enable-Location-Access-On-Web.md create mode 100644 docs/articles/expensify-classic/integrations/accounting-integrations/Bill-dot-com.md create mode 100644 docs/articles/expensify-classic/integrations/accounting-integrations/FinancalForce.md create mode 100644 docs/articles/expensify-classic/integrations/accounting-integrations/NetSuite.md create mode 100644 docs/articles/expensify-classic/integrations/accounting-integrations/QuickBooks-Desktop.md create mode 100644 docs/articles/expensify-classic/integrations/accounting-integrations/QuickBooks-Online.md create mode 100644 docs/articles/expensify-classic/integrations/accounting-integrations/Sage-Intacct.md create mode 100644 docs/articles/expensify-classic/integrations/accounting-integrations/Xero.md create mode 100644 docs/articles/expensify-classic/integrations/hr-integrations/ADP.md create mode 100644 docs/articles/expensify-classic/integrations/hr-integrations/Greenhouse.md create mode 100644 docs/articles/expensify-classic/integrations/hr-integrations/Gusto.md create mode 100644 docs/articles/expensify-classic/integrations/hr-integrations/QuickBooks-Time.md create mode 100644 docs/articles/expensify-classic/integrations/hr-integrations/Rippling.md create mode 100644 docs/articles/expensify-classic/integrations/hr-integrations/Workday.md create mode 100644 docs/articles/expensify-classic/integrations/hr-integrations/Zenefits.md create mode 100644 docs/articles/expensify-classic/integrations/other-integrations/Google-Apps-SSO.md create mode 100644 docs/articles/expensify-classic/integrations/travel-integrations/Bolt.md create mode 100644 docs/articles/expensify-classic/integrations/travel-integrations/Egencia.md create mode 100644 docs/articles/expensify-classic/integrations/travel-integrations/Global-VaTax.md create mode 100644 docs/articles/expensify-classic/integrations/travel-integrations/Grab.md create mode 100644 docs/articles/expensify-classic/integrations/travel-integrations/Hotel-Tonight.md create mode 100644 docs/articles/expensify-classic/integrations/travel-integrations/Kayak.md create mode 100644 docs/articles/expensify-classic/integrations/travel-integrations/Lyft.md create mode 100644 docs/articles/expensify-classic/integrations/travel-integrations/TrainLine.md create mode 100644 docs/articles/expensify-classic/integrations/travel-integrations/TravelPerk.md create mode 100644 docs/articles/expensify-classic/integrations/travel-integrations/Trip-Actions.md create mode 100644 docs/articles/expensify-classic/integrations/travel-integrations/TripCatcher.md create mode 100644 docs/articles/expensify-classic/integrations/travel-integrations/Uber.md create mode 100644 docs/articles/expensify-classic/manage-employees-and-report-approvals/Adding-Users.md create mode 100644 docs/articles/expensify-classic/manage-employees-and-report-approvals/Approval-Workflows.md create mode 100644 docs/articles/expensify-classic/manage-employees-and-report-approvals/Approving-Reports.md create mode 100644 docs/articles/expensify-classic/manage-employees-and-report-approvals/User-Roles.md create mode 100644 docs/articles/expensify-classic/manage-employees-and-report-approvals/Vacation-Delegate.md create mode 100644 docs/articles/expensify-classic/policy-and-domain-settings/Admins.md create mode 100644 docs/articles/expensify-classic/policy-and-domain-settings/Categories.md create mode 100644 docs/articles/expensify-classic/policy-and-domain-settings/Domain-Admins.md create mode 100644 docs/articles/expensify-classic/policy-and-domain-settings/Domain-Members.md create mode 100644 docs/articles/expensify-classic/policy-and-domain-settings/Domains-Overview.md create mode 100644 docs/articles/expensify-classic/policy-and-domain-settings/Expenses.md create mode 100644 docs/articles/expensify-classic/policy-and-domain-settings/Invoicing.md create mode 100644 docs/articles/expensify-classic/policy-and-domain-settings/Overview.md create mode 100644 docs/articles/expensify-classic/policy-and-domain-settings/Per-Diem.md create mode 100644 docs/articles/expensify-classic/policy-and-domain-settings/Reimbursement.md create mode 100644 docs/articles/expensify-classic/policy-and-domain-settings/Reports.md create mode 100644 docs/articles/expensify-classic/policy-and-domain-settings/SAML.md create mode 100644 docs/articles/expensify-classic/policy-and-domain-settings/Tags.md create mode 100644 docs/articles/expensify-classic/policy-and-domain-settings/Tax.md create mode 100644 docs/articles/expensify-classic/policy-and-domain-settings/Trips.md create mode 100644 docs/articles/expensify-classic/send-payments/Pay-Bills.md create mode 100644 docs/articles/expensify-classic/send-payments/Pay-Invoices.md create mode 100644 docs/articles/expensify-classic/send-payments/Reimbursing-Reports.md create mode 100644 docs/articles/expensify-classic/send-payments/Third-Party-Payments.md diff --git a/docs/articles/expensify-classic/account-settings/Account-Access.md b/docs/articles/expensify-classic/account-settings/Account-Access.md new file mode 100644 index 000000000000..f04b45c42639 --- /dev/null +++ b/docs/articles/expensify-classic/account-settings/Account-Access.md @@ -0,0 +1,5 @@ +--- +title: Account Access +description: Account Access +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/account-settings/Close-Account.md b/docs/articles/expensify-classic/account-settings/Close-Account.md new file mode 100644 index 000000000000..cf5052fa56f1 --- /dev/null +++ b/docs/articles/expensify-classic/account-settings/Close-Account.md @@ -0,0 +1,5 @@ +--- +title: Close Account +description: Close Account +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/account-settings/Merge-Accounts.md b/docs/articles/expensify-classic/account-settings/Merge-Accounts.md new file mode 100644 index 000000000000..1c5f22478e17 --- /dev/null +++ b/docs/articles/expensify-classic/account-settings/Merge-Accounts.md @@ -0,0 +1,5 @@ +--- +title: Merge Accounts +description: Merge Accounts +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/account-settings/Preferences.md b/docs/articles/expensify-classic/account-settings/Preferences.md new file mode 100644 index 000000000000..a3e53e1177a1 --- /dev/null +++ b/docs/articles/expensify-classic/account-settings/Preferences.md @@ -0,0 +1,5 @@ +--- +title: Preferences +description: Preferences +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/account-settings/Profile-Settings.md b/docs/articles/expensify-classic/account-settings/Profile-Settings.md new file mode 100644 index 000000000000..bdc18036a46e --- /dev/null +++ b/docs/articles/expensify-classic/account-settings/Profile-Settings.md @@ -0,0 +1,5 @@ +--- +title: Profile Settings +description: Profile Settings +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Business-Bank-Accounts-AUS.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Business-Bank-Accounts-AUS.md new file mode 100644 index 000000000000..44488defcd67 --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Business-Bank-Accounts-AUS.md @@ -0,0 +1,5 @@ +--- +title: Business Bank Accounts - AUS +description: Business Bank Accounts - AUS +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Business-Bank-Accounts-USD.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Business-Bank-Accounts-USD.md new file mode 100644 index 000000000000..218d6dcd1efa --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Business-Bank-Accounts-USD.md @@ -0,0 +1,5 @@ +--- +title: Business Bank Accounts - USD +description: Business Bank Accounts - USD +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Deposit-Accounts-AUS.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Deposit-Accounts-AUS.md new file mode 100644 index 000000000000..dba02f6fc52c --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Deposit-Accounts-AUS.md @@ -0,0 +1,5 @@ +--- +title: Deposit Accounts - AUS +description: Deposit Accounts - AUS +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Deposit-Accounts-USD.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Deposit-Accounts-USD.md new file mode 100644 index 000000000000..8d3fe6e51484 --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Deposit-Accounts-USD.md @@ -0,0 +1,5 @@ +--- +title: Deposit Accounts - USD +description: Deposit Accounts - USD +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Global-Reimbursement.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Global-Reimbursement.md new file mode 100644 index 000000000000..40bdfb7741ab --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Global-Reimbursement.md @@ -0,0 +1,5 @@ +--- +title: Global Reimbursement +description: Global Reimbursement +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Personal-Credit-Cards.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Personal-Credit-Cards.md new file mode 100644 index 000000000000..016ca90ee7f7 --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Personal-Credit-Cards.md @@ -0,0 +1,5 @@ +--- +title: Personal Credit Cards +description: Personal Credit Cards +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/ANZ.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/ANZ.md new file mode 100644 index 000000000000..6bfc7b14c09a --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/ANZ.md @@ -0,0 +1,6 @@ +--- +title: ANZ +description: A guide to integrate with your ANZ card +--- +## Resources Coming Soon! +Coming Soon!! diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Brex.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Brex.md new file mode 100644 index 000000000000..7d5ad7bf0315 --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Brex.md @@ -0,0 +1,5 @@ +--- +title: Brex +description: Brex +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/CSV-Import.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/CSV-Import.md new file mode 100644 index 000000000000..db68d4431a3a --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/CSV-Import.md @@ -0,0 +1,5 @@ +--- +title: CSV Import +description: CSV Import +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Commercial-Card-Feeds.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Commercial-Card-Feeds.md new file mode 100644 index 000000000000..e49d0d61855c --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Commercial-Card-Feeds.md @@ -0,0 +1,5 @@ +--- +title: Commercial Card Feeds +description: Commercial Card Feeds +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Connect-Company-Cards.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Connect-Company-Cards.md new file mode 100644 index 000000000000..ecd4fc0a6538 --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Connect-Company-Cards.md @@ -0,0 +1,5 @@ +--- +title: Connect Company Cards +description: Connect Company Cards +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Direct-Bank-Connections.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Direct-Bank-Connections.md new file mode 100644 index 000000000000..6775b2684b61 --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Direct-Bank-Connections.md @@ -0,0 +1,5 @@ +--- +title: Direct Bank Connections +description: Direct Bank Connections +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Export-To-GL-Accounts.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Export-To-GL-Accounts.md new file mode 100644 index 000000000000..58485888b921 --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Export-To-GL-Accounts.md @@ -0,0 +1,5 @@ +--- +title: Export to GL Accounts +description: Export to GL Accounts +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Reconciliation.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Reconciliation.md new file mode 100644 index 000000000000..be400ee2c13c --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Reconciliation.md @@ -0,0 +1,5 @@ +--- +title: Reconciliation +description: Reconciliation +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Troubleshooting.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Troubleshooting.md new file mode 100644 index 000000000000..d9e0d1bb994b --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Troubleshooting.md @@ -0,0 +1,5 @@ +--- +title: Troubleshooting +description: Troubleshooting +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/billing-and-subscriptions/Annual-Subscription.md b/docs/articles/expensify-classic/billing-and-subscriptions/Annual-Subscription.md new file mode 100644 index 000000000000..c80a0d57400d --- /dev/null +++ b/docs/articles/expensify-classic/billing-and-subscriptions/Annual-Subscription.md @@ -0,0 +1,5 @@ +--- +title: Annual Subscription +description: Annual Subscription +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/billing-and-subscriptions/Billing-Owner.md b/docs/articles/expensify-classic/billing-and-subscriptions/Billing-Owner.md new file mode 100644 index 000000000000..590fbc78007e --- /dev/null +++ b/docs/articles/expensify-classic/billing-and-subscriptions/Billing-Owner.md @@ -0,0 +1,5 @@ +--- +title: Billing-Owner +description: Billing-Owner +--- +## Resources Coming Soon! \ No newline at end of file diff --git a/docs/articles/expensify-classic/billing-and-subscriptions/Change-Plan-Or-Subscription.md b/docs/articles/expensify-classic/billing-and-subscriptions/Change-Plan-Or-Subscription.md new file mode 100644 index 000000000000..2f593625a7d5 --- /dev/null +++ b/docs/articles/expensify-classic/billing-and-subscriptions/Change-Plan-Or-Subscription.md @@ -0,0 +1,5 @@ +--- +title: Change Plan or Subscription +description: Change Plan or Subscription +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/billing-and-subscriptions/Consolidated-Domain-Billing.md b/docs/articles/expensify-classic/billing-and-subscriptions/Consolidated-Domain-Billing.md new file mode 100644 index 000000000000..de6ec4a4a466 --- /dev/null +++ b/docs/articles/expensify-classic/billing-and-subscriptions/Consolidated-Domain-Billing.md @@ -0,0 +1,5 @@ +--- +title: Consolidated Domain Billing +description: Consolidated Domain Billing +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/billing-and-subscriptions/Free-Trial.md b/docs/articles/expensify-classic/billing-and-subscriptions/Free-Trial.md new file mode 100644 index 000000000000..8a7b7edd19d9 --- /dev/null +++ b/docs/articles/expensify-classic/billing-and-subscriptions/Free-Trial.md @@ -0,0 +1,5 @@ +--- +title: Free Trial +description: Free Trial +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/billing-and-subscriptions/Individual-Subscription.md b/docs/articles/expensify-classic/billing-and-subscriptions/Individual-Subscription.md new file mode 100644 index 000000000000..d6be489a1146 --- /dev/null +++ b/docs/articles/expensify-classic/billing-and-subscriptions/Individual-Subscription.md @@ -0,0 +1,5 @@ +--- +title: Individual Subscription +description: Individual Subscription +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/billing-and-subscriptions/Overview.md b/docs/articles/expensify-classic/billing-and-subscriptions/Overview.md new file mode 100644 index 000000000000..3352c72167cd --- /dev/null +++ b/docs/articles/expensify-classic/billing-and-subscriptions/Overview.md @@ -0,0 +1,5 @@ +--- +title: Overview +description: Overview +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/billing-and-subscriptions/Pay-Per-Use-Subscription.md b/docs/articles/expensify-classic/billing-and-subscriptions/Pay-Per-Use-Subscription.md new file mode 100644 index 000000000000..be431a287557 --- /dev/null +++ b/docs/articles/expensify-classic/billing-and-subscriptions/Pay-Per-Use-Subscription.md @@ -0,0 +1,5 @@ +--- +title: Pay-per-use Subscription +description: Pay-per-use Subscription +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/billing-and-subscriptions/Payment-Card.md b/docs/articles/expensify-classic/billing-and-subscriptions/Payment-Card.md new file mode 100644 index 000000000000..91c5d4e91eda --- /dev/null +++ b/docs/articles/expensify-classic/billing-and-subscriptions/Payment-Card.md @@ -0,0 +1,5 @@ +--- +title: Payment Card +description: Payment Card +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/billing-and-subscriptions/Tax-Exempt.md b/docs/articles/expensify-classic/billing-and-subscriptions/Tax-Exempt.md new file mode 100644 index 000000000000..c8f781cbd59b --- /dev/null +++ b/docs/articles/expensify-classic/billing-and-subscriptions/Tax-Exempt.md @@ -0,0 +1,5 @@ +--- +title: Tax Exempt +description: Tax Exempt +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/expense-and-report-features/Attendee-Tracking.md b/docs/articles/expensify-classic/expense-and-report-features/Attendee-Tracking.md new file mode 100644 index 000000000000..bc7fbdfe84aa --- /dev/null +++ b/docs/articles/expensify-classic/expense-and-report-features/Attendee-Tracking.md @@ -0,0 +1,5 @@ +--- +title: Attendee Tracking +description: Attendee Tracking +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/expense-and-report-features/Currency.md b/docs/articles/expensify-classic/expense-and-report-features/Currency.md new file mode 100644 index 000000000000..611365aa5013 --- /dev/null +++ b/docs/articles/expensify-classic/expense-and-report-features/Currency.md @@ -0,0 +1,5 @@ +--- +title: Currency +description: Currency +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/expense-and-report-features/Expense-Rules.md b/docs/articles/expensify-classic/expense-and-report-features/Expense-Rules.md new file mode 100644 index 000000000000..81c664497e14 --- /dev/null +++ b/docs/articles/expensify-classic/expense-and-report-features/Expense-Rules.md @@ -0,0 +1,5 @@ +--- +title: Expense Rules +description: Expense Rules +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/expense-and-report-features/Expense-Types.md b/docs/articles/expensify-classic/expense-and-report-features/Expense-Types.md new file mode 100644 index 000000000000..a75209e4dfb1 --- /dev/null +++ b/docs/articles/expensify-classic/expense-and-report-features/Expense-Types.md @@ -0,0 +1,5 @@ +--- +title: Expense Types +description: Expense Types +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/expense-and-report-features/Report-Comments.md b/docs/articles/expensify-classic/expense-and-report-features/Report-Comments.md new file mode 100644 index 000000000000..3938c02bd333 --- /dev/null +++ b/docs/articles/expensify-classic/expense-and-report-features/Report-Comments.md @@ -0,0 +1,5 @@ +--- +title: Report Comments +description: Report Comments +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/expense-and-report-features/The-Expenses-Page.md b/docs/articles/expensify-classic/expense-and-report-features/The-Expenses-Page.md new file mode 100644 index 000000000000..f202587568e5 --- /dev/null +++ b/docs/articles/expensify-classic/expense-and-report-features/The-Expenses-Page.md @@ -0,0 +1,5 @@ +--- +title: The Expenses Page +description: The Expenses Page +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/expense-and-report-features/The-Reports-Page.md b/docs/articles/expensify-classic/expense-and-report-features/The-Reports-Page.md new file mode 100644 index 000000000000..37da613e750a --- /dev/null +++ b/docs/articles/expensify-classic/expense-and-report-features/The-Reports-Page.md @@ -0,0 +1,5 @@ +--- +title: The Reports Page +description: The Reports Page +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/expensify-card/Auto-Reconciliation.md b/docs/articles/expensify-classic/expensify-card/Auto-Reconciliation.md new file mode 100644 index 000000000000..e1d1a990b166 --- /dev/null +++ b/docs/articles/expensify-classic/expensify-card/Auto-Reconciliation.md @@ -0,0 +1,5 @@ +--- +title: Auto-reconciliation +description: Auto-reconciliation +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/expensify-card/CPA-Card.md b/docs/articles/expensify-classic/expensify-card/CPA-Card.md new file mode 100644 index 000000000000..9f4c47a6a402 --- /dev/null +++ b/docs/articles/expensify-classic/expensify-card/CPA-Card.md @@ -0,0 +1,5 @@ +--- +title: CPA Card +description: CPA Card +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/expensify-card/Card-Settings.md b/docs/articles/expensify-classic/expensify-card/Card-Settings.md new file mode 100644 index 000000000000..ff9a959d38aa --- /dev/null +++ b/docs/articles/expensify-classic/expensify-card/Card-Settings.md @@ -0,0 +1,5 @@ +--- +title: Card Settings +description: Card Settings +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/expensify-card/Connect-To-Indirect-Integration.md b/docs/articles/expensify-classic/expensify-card/Connect-To-Indirect-Integration.md new file mode 100644 index 000000000000..0e05269f6501 --- /dev/null +++ b/docs/articles/expensify-classic/expensify-card/Connect-To-Indirect-Integration.md @@ -0,0 +1,5 @@ +--- +title: Connect to Indirect Integration +description: Connect to Indirect Integration +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/expensify-card/File-A-Dispute.md b/docs/articles/expensify-classic/expensify-card/File-A-Dispute.md new file mode 100644 index 000000000000..296999410687 --- /dev/null +++ b/docs/articles/expensify-classic/expensify-card/File-A-Dispute.md @@ -0,0 +1,5 @@ +--- +title: File a Dispute +description: File a Dispute +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/expensify-card/Get-The-Card.md b/docs/articles/expensify-classic/expensify-card/Get-The-Card.md new file mode 100644 index 000000000000..9c8e804f6363 --- /dev/null +++ b/docs/articles/expensify-classic/expensify-card/Get-The-Card.md @@ -0,0 +1,5 @@ +--- +title: Get the Card +description: Get the Card +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/expensify-card/Statements.md b/docs/articles/expensify-classic/expensify-card/Statements.md new file mode 100644 index 000000000000..602fa610dd0b --- /dev/null +++ b/docs/articles/expensify-classic/expensify-card/Statements.md @@ -0,0 +1,5 @@ +--- +title: Statements +description: Statements +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/expensify-card/The-Reports-Page.md b/docs/articles/expensify-classic/expensify-card/The-Reports-Page.md new file mode 100644 index 000000000000..37da613e750a --- /dev/null +++ b/docs/articles/expensify-classic/expensify-card/The-Reports-Page.md @@ -0,0 +1,5 @@ +--- +title: The Reports Page +description: The Reports Page +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/exports/Custom-Templates.md b/docs/articles/expensify-classic/exports/Custom-Templates.md new file mode 100644 index 000000000000..5dcfe58b09f5 --- /dev/null +++ b/docs/articles/expensify-classic/exports/Custom-Templates.md @@ -0,0 +1,5 @@ +--- +title: Custom Templates +description: Custom Templates +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/exports/Default-Export-Templates.md b/docs/articles/expensify-classic/exports/Default-Export-Templates.md new file mode 100644 index 000000000000..4dcb624698af --- /dev/null +++ b/docs/articles/expensify-classic/exports/Default-Export-Templates.md @@ -0,0 +1,5 @@ +--- +title: Default Export Templates +description: Default Export Templates +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/exports/Insights.md b/docs/articles/expensify-classic/exports/Insights.md new file mode 100644 index 000000000000..682c2a251228 --- /dev/null +++ b/docs/articles/expensify-classic/exports/Insights.md @@ -0,0 +1,100 @@ +--- +title: Custom Reporting and Insights +description: How to get the most out of the Custom Reporing and Insights +--- + +{% raw %} +# What is Custom Reporting and Insights? +The Insights dashboard allows you to monitor all aspects of company spend across categories, employees, projects, departments, and more. You can see trends in real time, forecast company budgets, and build unlimited custom reports with help from our trained specialist team. + +![Insights Pie Chart](https://help.expensify.com/assets/images/insights-chart.png){:width="100%"} +## Review your Insights data + +1. Navigate to your [Insights page](https://www.expensify.com/expenses?param={"fromInsightsTab":true,"viewMode":"charts"}), located in the left hand menu +2. Select a specific date range (the default view has the current month pre-selected) +3. Use the filter options to select the categories, tags, employees etc that you want insights on +4. Make sure that View in the top right corner is set to the pie chart icon +5. You can view any dataset in more detail by clicking in the “View Raw Data” column + +## Export your Insights data + +1. Switch the View in the top right corner of the [Insights page](https://www.expensify.com/expenses?param={"fromInsightsTab":true,"viewMode":"charts"}) to the lists icon +2. Select the expenses you want to export, either by selecting individual expenses, or checking the select all box (next to Date at the top) +3. Select **Export To** in the top right hand corner to download the report as a .csv file + +## Create a Custom Export Report for your Expenses + +1. Navigate to **Settings > Account > Preferences > scroll down to CSV Export Formats** +2. Build up a report using these [formulas](https://community.expensify.com/discussion/5795/deep-dive-expense-level-formula/p1?new=1) +3. Click the **Custom Export** button on the Insights page and your Account Manager will help get you started on building up your report + +## Create a Custom Export Report for your Policy + +1. Navigate to **Settings > Policies > Group > [Policy Name] > Export Formats** +2. Build up a report using these [formulas](https://community.expensify.com/discussion/5795/deep-dive-expense-level-formula/p1?new=1) +3. If you need any help, click the **Support** button on the top left to contact your Account Manager + +# FAQs + +#### Can I share my custom export report? + +If you would like to create a custom export report that can be shared with other policy admins, you can create these by navigating to the **[Settings > Policies > Group > [Policy Name] > Export Formats](https://www.expensify.com/admin_policies?param={"section":"group"})** page. Custom export reports created under **Settings > Account > Preferences** page are only available to the member who created them. + +#### Can I put expenses from different policies on the same report? + +Custom export reports created under Settings > Account > Preferences page are able to export expenses from multiple policies, and custom export formats created under Settings > Policies > Group > [Policy Name] > Export Formats are for expenses reported under that policy only. + +#### Are there any default export reports available? + +Yes! We have [seven default reports](https://community.expensify.com/discussion/5602/deep-dive-default-export-templates) available to export directly from the Reports page: + +- **All Data** - Expense Level Export** - the name says it all! This is for the people who want ALL the details from their expense reports. We're talking Tax, Merchant Category Codes, Approvers - you name it, this report's got it! +- **All Data** - Report Level Export - this is the report for those who don't need to see each individual expense but want to see a line by line breakdown at a report level - submitter, total amount, report ID - that kind of stuff +- **Basic Export** - this is the best way to get a simple breakdown of all your expenses - just the basics +- **Canadian Multiple Tax Export** - tax, GST, PST...if you need to know tax then this is the export you want! +- **Category Export** - want to see a breakdown of your expenses by Category? This is the export you +- **Per Diem Export** - the name says it all +- **Tag Export** - much like the Category Export, but for Tags + +*To note: these reports will be emailed directly to your email address rather than downloaded on your computer.* + +#### How many expenses can I export in one report? +The custom export reports are best for small-to-medium chunks of data. If you want to export large amounts of data, we recommend you use a [default export report](https://community.expensify.com/discussion/5602/deep-dive-default-export-templates) that you can run from the Reports page. + +#### What other kinds of export reports can my Account Manager help me create? + +We’ve built a huge variety of custom reports for customers, so make sure to reach out to your Account Manager for more details. Some examples of custom reports we’ve build for customers before are: + +- Accrual Report +- Aged Approval Reports +- Attendee Reporting +- Audit Report +- Candidate Spend +- Category Spend Report +- Department/Project Spend Report +- Duplication Report +- Duty of Care +- Efficiency +- Employee Bank Account Status +- Employee Details +- Employee Roles +- Expense Authorizations by Country +- Expense Reports by Country +- Expense Reports not posted to finance system +- Foreign Currency Transaction +- Fringe Benefit Tax Report +- HR Report +- Invoice Billed Transaction Reconciliation +- Mileage Reports +- Out of Pocket Expenses for Reimbursement +- Per Diem Report +- Reconciliation: Accounting, Bank Statement, Billed Transaction +- Rejected Report +- Travel Rule Class +- Travel Spend +- Unposted Cash Advance Report +- Unposted Procurement Aging Report +- Unposted Travel Aging Report +- Vendor Spend +- … or anything you can imagine! +{% endraw %} \ No newline at end of file diff --git a/docs/articles/expensify-classic/exports/The-Reports-Page.md b/docs/articles/expensify-classic/exports/The-Reports-Page.md new file mode 100644 index 000000000000..37da613e750a --- /dev/null +++ b/docs/articles/expensify-classic/exports/The-Reports-Page.md @@ -0,0 +1,5 @@ +--- +title: The Reports Page +description: The Reports Page +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/get-paid-back/Mileage.md b/docs/articles/expensify-classic/get-paid-back/Mileage.md new file mode 100644 index 000000000000..381bc28626f9 --- /dev/null +++ b/docs/articles/expensify-classic/get-paid-back/Mileage.md @@ -0,0 +1,5 @@ +--- +title: Mileage +description: Mileage +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/get-paid-back/Per-Diem.md b/docs/articles/expensify-classic/get-paid-back/Per-Diem.md new file mode 100644 index 000000000000..e5a57fc62bdf --- /dev/null +++ b/docs/articles/expensify-classic/get-paid-back/Per-Diem.md @@ -0,0 +1,5 @@ +--- +title: Per Diem +description: Per Diem +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/get-paid-back/Third-Party-Payments.md b/docs/articles/expensify-classic/get-paid-back/Third-Party-Payments.md new file mode 100644 index 000000000000..d472e54778e1 --- /dev/null +++ b/docs/articles/expensify-classic/get-paid-back/Third-Party-Payments.md @@ -0,0 +1,5 @@ +--- +title: Third Party Payments +description: Third Party Payments +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/get-paid-back/Trips.md b/docs/articles/expensify-classic/get-paid-back/Trips.md new file mode 100644 index 000000000000..3499865c4ee9 --- /dev/null +++ b/docs/articles/expensify-classic/get-paid-back/Trips.md @@ -0,0 +1,5 @@ +--- +title: Trips +description: Trips +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/get-paid-back/expenses/Apply-Tax.md b/docs/articles/expensify-classic/get-paid-back/expenses/Apply-Tax.md new file mode 100644 index 000000000000..224b622cec3f --- /dev/null +++ b/docs/articles/expensify-classic/get-paid-back/expenses/Apply-Tax.md @@ -0,0 +1,5 @@ +--- +title: Apply Tax +description: Apply Tax +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/get-paid-back/expenses/Create-Expenses.md b/docs/articles/expensify-classic/get-paid-back/expenses/Create-Expenses.md new file mode 100644 index 000000000000..8f4d035e1fe7 --- /dev/null +++ b/docs/articles/expensify-classic/get-paid-back/expenses/Create-Expenses.md @@ -0,0 +1,5 @@ +--- +title: Create Expenses +description: Create Expenses +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/get-paid-back/expenses/Merge-Expenses.md b/docs/articles/expensify-classic/get-paid-back/expenses/Merge-Expenses.md new file mode 100644 index 000000000000..c628244c9b2e --- /dev/null +++ b/docs/articles/expensify-classic/get-paid-back/expenses/Merge-Expenses.md @@ -0,0 +1,5 @@ +--- +title: Merge Expenses +description: Merge Expenses +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/get-paid-back/expenses/Upload-Receipts.md b/docs/articles/expensify-classic/get-paid-back/expenses/Upload-Receipts.md new file mode 100644 index 000000000000..2091b5f3e7f0 --- /dev/null +++ b/docs/articles/expensify-classic/get-paid-back/expenses/Upload-Receipts.md @@ -0,0 +1,5 @@ +--- +title: Upload Receipts +description: Upload Receipts +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/get-paid-back/reports/Create-A-Report.md b/docs/articles/expensify-classic/get-paid-back/reports/Create-A-Report.md new file mode 100644 index 000000000000..e6cc65290e73 --- /dev/null +++ b/docs/articles/expensify-classic/get-paid-back/reports/Create-A-Report.md @@ -0,0 +1,5 @@ +--- +title: Create a Report +description: Create a Report +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/get-paid-back/reports/Reimbursements.md b/docs/articles/expensify-classic/get-paid-back/reports/Reimbursements.md new file mode 100644 index 000000000000..91c4459d2ebd --- /dev/null +++ b/docs/articles/expensify-classic/get-paid-back/reports/Reimbursements.md @@ -0,0 +1,5 @@ +--- +title: Reimbursements +description: Reimbursements +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/getting-started/Best-Practices.md b/docs/articles/expensify-classic/getting-started/Best-Practices.md new file mode 100644 index 000000000000..16b284ae60df --- /dev/null +++ b/docs/articles/expensify-classic/getting-started/Best-Practices.md @@ -0,0 +1,5 @@ +--- +title: Best Practices +description: Best Practices +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/getting-started/Employees.md b/docs/articles/expensify-classic/getting-started/Employees.md new file mode 100644 index 000000000000..f139c40be926 --- /dev/null +++ b/docs/articles/expensify-classic/getting-started/Employees.md @@ -0,0 +1,5 @@ +--- +title: Employees +description: Employees +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/getting-started/Individual-Users.md b/docs/articles/expensify-classic/getting-started/Individual-Users.md new file mode 100644 index 000000000000..2e152ea515d7 --- /dev/null +++ b/docs/articles/expensify-classic/getting-started/Individual-Users.md @@ -0,0 +1,5 @@ +--- +title: Individual Users +description: Individual Users +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/getting-started/Invite-Employees.md b/docs/articles/expensify-classic/getting-started/Invite-Employees.md new file mode 100644 index 000000000000..5cdb8eb086b0 --- /dev/null +++ b/docs/articles/expensify-classic/getting-started/Invite-Employees.md @@ -0,0 +1,5 @@ +--- +title: Invite Employees +description: Invite Employees +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/getting-started/Plan-Types.md b/docs/articles/expensify-classic/getting-started/Plan-Types.md new file mode 100644 index 000000000000..7bb725a1aa35 --- /dev/null +++ b/docs/articles/expensify-classic/getting-started/Plan-Types.md @@ -0,0 +1,5 @@ +--- +title: Plan-Types +description: Plan-Types +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/getting-started/Policy-Admins.md b/docs/articles/expensify-classic/getting-started/Policy-Admins.md new file mode 100644 index 000000000000..91d56b0c4f71 --- /dev/null +++ b/docs/articles/expensify-classic/getting-started/Policy-Admins.md @@ -0,0 +1,5 @@ +--- +title: Policy Admins +description: Policy Admins +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/getting-started/Referral-Program.md b/docs/articles/expensify-classic/getting-started/Referral-Program.md new file mode 100644 index 000000000000..683e93d0277a --- /dev/null +++ b/docs/articles/expensify-classic/getting-started/Referral-Program.md @@ -0,0 +1,53 @@ +--- +title: Expensify Referral Program +description: Send your joining link, submit a receipt or invoice, and we'll pay you if your referral adopts Expensify. +--- + + +# About + +Expensify has grown thanks to our users who love Expensify so much that they tell their friends, colleagues, managers, and fellow business founders to use it, too. + +As a thank you, every time you bring a new user into the platform who directly or indirectly leads to the adoption of a paid annual plan on Expensify, you will earn $250. + +# How to get paid for referring people to Expensify + +1. Submit a report or invoice, or share your referral link with anyone you know who is spending too much time on expenses, or works at a company that could benefit from using Expensify. + +2. You will get $250 for any referred business that commits to an annual subscription, has 2 or more active users, and makes two monthly payments. + +That’s right! You can refer anyone working at any company you know. + +If their company goes on to become an Expensify customer with an annual subscription, and you are the earliest recorded referrer of a user on that company’s paid Expensify Policy, you'll get paid a referral reward. + +The best way to start is to submit any receipt to your manager (you'll get paid back and set yourself up for $250 if they start a subscription: win-win!) + +Referral rewards for the Spring/Summer 2023 campaign will be paid by direct deposit. + +# FAQ + +- **How will I know if I am the first person to refer a company to Expensify?** + +Successful referrers are notified after their referral pays for 2 months of an annual subscription. We will check for the earliest recorded referrer of a user on the policy, and if that is you, then we will let you know. + +- **How will you pay me if I am successful?** + +In the Spring 2023 campaign, Expensify will be paying successful referrers via direct deposit to the Deposit-Only account you have on file. Referral payouts will happen once a month for the duration of the campaign. If you do not have a Deposit-Only account at the time of your referral payout, your deposit will be processed in the next batch. + +Learn how to add a Deposit-Only account [here](https://community.expensify.com/discussion/4641/how-to-add-a-deposit-only-bank-account-both-personal-and-business). + +- **I’m outside of the US, how do I get paid?** + +While our referral payouts are in USD, you will be able to get paid via a Wise Borderless account. Learn more [here](https://community.expensify.com/discussion/5940/how-to-get-reimbursed-outside-the-us-with-wise-for-non-us-employees). + +- **My referral wasn’t counted! How can I appeal?** + +Expensify reserves the right to modify the terms of the referral program at any time, and pays out referral bonuses for eligible companies at its own discretion. + +Please send a message to concierge@expensify.com with the billing owner of the company you have referred and our team will review the referral and get back to you. + +- **Where can I find my referral link?** + +Expensify members who are opted-in for our newsletters will have received an email containing their unique referral link. + +On the mobile app, go to **Settings** > **Invite a Friend** > **Share Invite Link** to retrieve your referral link. diff --git a/docs/articles/expensify-classic/getting-started/Security.md b/docs/articles/expensify-classic/getting-started/Security.md new file mode 100644 index 000000000000..41451e2ba958 --- /dev/null +++ b/docs/articles/expensify-classic/getting-started/Security.md @@ -0,0 +1,5 @@ +--- +title: Security +description: Security +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/getting-started/Support/Your-Expensify-Account-Manager.md b/docs/articles/expensify-classic/getting-started/Support/Your-Expensify-Account-Manager.md new file mode 100644 index 000000000000..3ef47337a74c --- /dev/null +++ b/docs/articles/expensify-classic/getting-started/Support/Your-Expensify-Account-Manager.md @@ -0,0 +1,36 @@ +--- +title: Your Expensify Account Manager +description: Everything you need to know about Having an Expensify account manager +--- + + + +# What is an account manager? +An account manager is a dedicated point of contact to support policy admins with questions about their Expensify account. They are available to help you and other policy admins review your account, advise on best practices, and make changes to your policy on your behalf whenever you need a hand. They will actively monitor open technical issues and be proactive with recommendations to increase efficiency and minimize time spent on expense management. + +Unlike Concierge, an account manager’s support will not be real-time, 24 hours a day. A benefit of Concierge is that you get real-time support every day. Your account manager will be super responsive when online, but anything sent when they’re offline will not be responded to until they’re online again. + +For real-time responses and simple troubleshooting issues, you can always message our general support by writing to Concierge via the in-product chat or by emailing concierge@expensify.com. + +# How do I know if I have an account manager? +If you are a policy admin or domain admin, you will also hear from your account manager as soon as one gets assigned to your company. If you'd like a reminder who your account manager is, just click the Support link on the left side of Expensify - you'll see your account manager's name and photo, with an option to contact them for help. + +## How do I contact my account manager? +We make it easy to contact your account manager: + +1. Log in to your Expensify account, click "Support" along the left side of the page, and click the “Account Manager” option +2. Reply to (or click the chat link on) any email you get from your account manager +3. Sign in to new.expensify.com and go to the #admins room for any of your policies. Your account manager is in your #admin rooms ready to help you, so you can ask for help here and your account manager will respond in the chat. + +# FAQs +## Who gets an account manager? +Every customer with 10 or more paid subscribers is automatically assigned a dedicated account manager. If you have fewer than 10 active users each month, you can still get an account manager by increasing your subscription to 10 or more users, To get assigned an account manager immediately, log into your Expensify account and go to Settings > Policies > Group, then click Subscription and increase your subscription size to 10 or more. + +## How do I know if my account manager is online? +You will be able to see if they are online via their status, which will either say something like “online” or have their working hours. + +## What if I’m unable to reach my account manager? +If for some reason, you’re unable to reach your account manager, perhaps because they’re offline, then you can always reach out to Concierge for assistance. Your account manager will always get back to you when they’re online again. + +## Can I get on a call with my account manager? +Of course! You can ask your account manager to schedule a call whenever you think one might be helpful. We usually find that the most effective calls are those that deal with general setup questions. For technical troubleshooting, we typically recommend chat as that allows your account manager time to look into the issue, test things on their end, and, if needed, consult the wider Expensify technical team. It also allows you to easily refer back to instructions and links. \ No newline at end of file diff --git a/docs/articles/expensify-classic/getting-started/Using-The-App.md b/docs/articles/expensify-classic/getting-started/Using-The-App.md new file mode 100644 index 000000000000..37767ea9d78d --- /dev/null +++ b/docs/articles/expensify-classic/getting-started/Using-The-App.md @@ -0,0 +1,5 @@ +--- +title: Using the App +description: Using the App +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/getting-started/approved-accountants/Card-Revenue-Share-For-Expensify-Approved-Partners.md b/docs/articles/expensify-classic/getting-started/approved-accountants/Card-Revenue-Share-For-Expensify-Approved-Partners.md new file mode 100644 index 000000000000..b18531d43200 --- /dev/null +++ b/docs/articles/expensify-classic/getting-started/approved-accountants/Card-Revenue-Share-For-Expensify-Approved-Partners.md @@ -0,0 +1,16 @@ +--- +title: Expensify Card revenue share for ExpensifyApproved! partners +description: Earn money when your clients adopt the Expensify Card +--- + + +Start making more with us! We're thrilled to announce a new incentive for our US-based ExpensifyApproved! partner accountants. You can now earn additional income for your firm every time your client uses their Expensify Card. **In short, your firm gets 0.5% of your clients’ total Expensify Card spend as cash back**. The more your clients spend, the more cashback your firm receives!
+
This program is currently only available to US-based ExpensifyApproved! partner accountants. + +# How-to +To benefit from this program, all you need to do is ensure that you are listed as a domain admin on your client's Expensify account. If you're not currently a domain admin, your client can follow the instructions outlined in [our help article](https://community.expensify.com/discussion/5749/how-to-add-and-remove-domain-admins#:~:text=Domain%20Admins%20have%20total%20control,a%20member%20of%20the%20domain.) to assign you this role. +# FAQ +- What if my firm is not permitted to accept revenue share from our clients?
+
We understand that different firms may have different policies. If your firm is unable to accept this revenue share, you can pass the revenue share back to your client to give them an additional 0.5% of cash back using your own internal payment tools.

+- What if my firm does not wish to participate in the program?
+
Please reach out to your assigned partner manager at new.expensify.com to inform them you would not like to accept the revenue share nor do you want to pass the revenue share to your clients. \ No newline at end of file diff --git a/docs/articles/expensify-classic/getting-started/approved-accountants/Your-Expensify-Partner-Manager.md b/docs/articles/expensify-classic/getting-started/approved-accountants/Your-Expensify-Partner-Manager.md new file mode 100644 index 000000000000..c7a5dc5a04ab --- /dev/null +++ b/docs/articles/expensify-classic/getting-started/approved-accountants/Your-Expensify-Partner-Manager.md @@ -0,0 +1,34 @@ +--- +title: Your Expensify Partner Manager +description: Everything you need to know about your Expensify Partner Manager +--- + + +# What is a Partner Manager? +A Partner Manager is a dedicated point of contact to support our ExpensifyApproved! Accountants with questions about their Expensify account. Partner Managers support our accounting partners by providing recommendations for client's accounts, assisting with firm-wide training, and ensuring partners receive the full benefits of our partnership program. They will actively monitor open technical issues and be proactive with recommendations to increase efficiency and minimize time spent on expense management. + +Unlike Concierge, a Partner Manager’s support will not be real-time, 24 hours a day. A benefit of Concierge is that you get real-time support every day. Your partner manager will be super responsive when online, but anything sent when they’re offline will not be responded to until they’re online again. + +For real-time responses and simple troubleshooting issues, you can always message our general support by writing to Concierge via the in-product chat or by emailing concierge@expensify.com. + +# How do I know if I have a Partner Manager? +For your firm to be assigned a Partner Manager, you must complete the [ExpensifyApproved! University](https://use.expensify.com/accountants) training course. Every external accountant or bookkeeper who completes the training is automatically enrolled in our program and receives all the benefits, including access to the Partner Manager. So everyone at your firm must complete the training to receive the maximum benefit. + +You can check to see if you’ve completed the course and enrolled in the ExpensifyApproved! Accountants program simply by logging into your Expensify account. In the bottom left-hand corner of the website, you will see the ExpensifyApproved! logo. + +# How do I contact my Partner Manager? +You can contact your Partner Manager by: +- Signing in to new.expensify.com and searching for your Partner Manager +- Replying to or clicking the chat link on any email you get from your Partner Manager + +# FAQs +## How do I know if my Partner Manager is online? +You will be able to see if they are online via their status in new.expensify.com, which will either say “online” or have their working hours. + +## What if I’m unable to reach my Partner Manager? +If you’re unable to contact your Partner Manager (i.e., they're out of office for the day) you can reach out to Concierge for assistance. Your Partner Manager will get back to you when they’re online again. + +## Can I get on a call with my Partner Manager? +Of course! You can ask your Partner Manager to schedule a call whenever you think one might be helpful. Partner Managers can discuss client onboarding strategies, firm wide training, and client setups. + +We recommend continuing to work with Concierge for **general support questions**, as this team is always online and available to help immediately. \ No newline at end of file diff --git a/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md b/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md new file mode 100644 index 000000000000..2b95a1d13fde --- /dev/null +++ b/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md @@ -0,0 +1,283 @@ +--- +title: Expensify Playbook for Small to Medium-Sized Businesses +description: Best practices for how to deploy Expensify for your business +--- +## Overview +This guide provides practical tips and recommendations for small businesses with 100 to 250 employees to effectively use Expensify to improve spend visibility, facilitate employee reimbursements, and reduce the risk of fraudulent expenses. + +- See our [US-based VC-Backed Startups](https://help.expensify.com/articles/playbooks/Expensify-Playbook-for-US-based-VC-Backed-Startups) if you are more concerned with top-line revenue growth + +## Who you are +As a small to medium-sized business owner, your main aim is to achieve success and grow your business. To achieve your goals, it is crucial that you make worthwhile investments in both your workforce and your business processes. This means providing your employees with the resources they need to generate revenue effectively, while also adopting measures to guarantee that expenses are compliant. + +## Step-by-step instructions for setting up Expensify +This playbook is built on best practices we’ve developed after processing expenses for tens of thousands of companies around the world. As such, use this playbook as your starting point, knowing that you can customize Expensify to suit your business needs. Every company is different, and your dedicated Setup Specialist is always one chat away with any questions you may have. + +### Step 1: Create your Expensify account +If you don't already have one, go to *[new.expensify.com](https://new.expensify.com)* and sign up for an account with your work email address. The account is free so don’t worry about the cost at this stage. + +> _Employees really appreciate how easy it is to use, and the fact that the reimbursement drops right into their bank account. Since most employees are submitting expenses from their phones, the ease of use of the app is critical_ +> +> **Robyn Gresham** +> Senior Accounting Systems Manager at SunCommon + +### Step 2: Create a Control Policy +There are three policy types, but for your small business needs we recommend the *Control Plan* for the following reasons: + +- *The Control Plan* is designed for organizations with a high volume of employee expense submissions, who also rely on compliance controls +- The ease of use and mobile-first design of the Control plan can increase employee adoption and participation, leading to better expense tracking and management. +- The plan integrates with a variety of tools, including accounting software and payroll systems, providing a seamless and integrated experience +- Accounting integrations include QuickBooks Online, Xero, NetSuite, and Sage Intacct, with indirect support from Microsoft Dynamics and any other accounting solution you work with + +We recommend creating one single policy for your US entity. This allows you to centrally manage all employees in one “group” while enforcing compliance controls and syncing with your accounting package accordingly. + +To create your Control Policy: + +1. Go to *Settings > Policies* +2. Select *Group* and click the button that says *New Policy* +3. Click *Select* under Control + +The Control Plan also gives you access to a dedicated Setup Specialist. You can find yours by looking at your policy's *#admins* room in *[new.expensify.com](https://new.expensify.com)*, and in your company’s policy settings in the *Overview* tab, where you can chat with them and schedule an onboarding call to walk through any setup questions. The Control Plan bundled with the Expensify Card is only *$9 per user per month* (not taking into account cash back your earn) when you commit annually. That’s a 75% discount off the unbundled price point if you choose to use a different Corporate Card (or no) provider. + +### Step 3: Connect your accounting system +As a small to medium-sized business, it's important to maintain proper spend management to ensure the success and stability of your organization. This requires paying close attention to your expenses, streamlining your financial processes, and making sure that your financial information is accurate, compliant, and transparent. Include best practices such as: + +- Every purchase is categorized into the correct account in your chart of accounts +- Receipts are sent to the accounting package to ensure visibility across the organization and to auditors +- Every expense is accounted for and added to your accounting system on time for your monthly accounts reconciliation. + +You do this by synchronizing Expensify and your accounting package as follows: + +1. Click *Settings > Policies* +2. Navigate to the *Connections* tab +3. Select your accounting system +4. Follow the prompts to connect your accounting package + +Check out the links below for more information on how to connect to your accounting solution: +- *[QuickBooks Online](https://community.expensify.com/discussion/4833/how-to-connect-your-policy-to-quickbooks-online)* +- *[Xero](https://community.expensify.com/discussion/5282/how-to-connect-your-policy-to-xero)* +- *[NetSuite](https://community.expensify.com/discussion/5212/how-to-connect-your-policy-to-netsuite-token-based-authentication)* +- *[Sage Intacct](https://community.expensify.com/discussion/4777/how-to-connect-to-sage-intacct-user-based-permissions-expense-reports)* +- *[Other Accounting System](https://community.expensify.com/discussion/5271/how-to-set-up-an-indirect-accounting-integration) + + +*“Employees really appreciate how easy it is to use, and the fact that the reimbursement drops right into their bank account. Since most employees are submitting expenses from their phones, the ease of use of the app is critical.”* +- Robyn Gresham, Senior Accounting Systems Manager at SunCommon + +### Step 4: Set category-specific compliance controls +Head over to the *Categories* tab to set compliance controls on your newly imported list of categories. More specifically, we recommend the following: + +1. First, enable *People Must Categorize Expenses*. Employees must select a category for each expense, otherwise, in most cases, it’s more work on you and our accounting connections will simply reject any attempt to export. +2. For more high-risk, travel-related categories, we recommend setting more strict compliance controls. For example, “Meals & Entertainment” should be set with the following: + - Receipts Required + - Descriptions Required, with Description Hints set + - Travel: “What is the business purpose of this expense?” + - Meals: “Could you share the business purpose, and tag attendees?” + - Entertainment: “Could you share the business purpose, and tag attendees?” +3. Disable any irrelevant expense categories that aren’t associated with employee spend +4. Configure *auto-categorization*, located just below your category list in the same tab. The section is titled *Default Categories*. Just find the right category, and match it with the presented category groups to allow for MCC (merchant category code) automated category selection with every imported connected card transaction. + +### Step 5: Make sure tags are required, or defaults are set +Tags in Expensify often relate to departments, projects/customers, classes, and so on. And in some cases they are *required* to be selected on every transactions. And in others, something like *departments* is a static field, meaning we could set it as an employee default and not enforce the tag selection with each expense. + +*Make Tags Required* +In the tags tab in your policy settings, you’ll notice the option to enable the “Required” field. This makes it so any time an employee doesn’t assign a tag to an expense, we’ll flag a violation on it and notify both the employee and the approver. + +- *Note:* In general, we take prior selection into account, so anytime you select a tag in Expensify, we’ll pre-populate that same field for any subsequent expense. It’s completely interchangeable, and there for convenience. + +*Set Tags as an Employee Default* +Separately, if your policy is connected to NetSuite or Sage Intacct, you can set departments, for example, as an employee default. All that means is we’ll apply the department (for example) that’s assigned to the employee record in your accounting package and apply that to every exported transaction, eliminating the need for the employee to have to manually select a department for each expense. + +### Step 6: Set rules for all expenses regardless of categorization +In the Expenses tab in your group Control policy, you’ll notice a *Violations* section designed to enforce top-level compliance controls that apply to every expense, for every employee in your policy. We recommend the following confiuration: + +*Max Expense Age: 90 days (or leave it blank)* +This will enable Expensify to catch employee reimbursement requests that are far too outdated for reimbursement, and present them as a violations. If you’d prefer a different time window, you can edit it accordingly + +*Max Expense Amount: $2,000 (or leave it blank)* +This is essentially like setting a daily or individual expense limitation on any employee, regardless of whether the transaction is reimbursable or non-reimbursable.This rule will enables Expensify to present larger expenses with a violation to notify both the submitter and approvers. + +*Receipt Required Amount: $75* +Receipts are important, and in most cases you prefer an itemized receipt. However, Expensify will generate an IRS-compliant electronic receipt (not itemized) for every expense not tied to hotels expense. For this reason, it’s important to enforce a rule where anytime an employee is on the road, and making business-related purchases at hotel (which happens a lot!), they are required to attach a physical receipt. + +![Expense Basics](https://help.expensify.com/assets/images/playbook-expense-basics.png){:width="100%"} + +At this point, you’ve set enough compliance controls around categorical spend and general expenses for all employees, such that you can put trust in our solution to audit all expenses up front so you don’t have to. Next, let’s dive into how we can comfortably take on more automation, while relying on compliance controls to capture bad behavior (or better yet, instill best practices in our employees). + +### Step 7: Set up scheduled submit +For an efficient company, we recommend setting up [Scheduled Submit](https://community.expensify.com/discussion/4476/how-to-enable-scheduled-submit-for-a-group-policy) on a *Daily* frequency: + +- Click *Settings > Policies* +- From here, select your group collect policy +- Within your policy settings, select the *Reports* tab +- You’ll notice *Scheduled Submit* is located directly under *Report Basics* +- Choose *Daily* + +Between Expensify's SmartScan technology, automatic categorization, and [DoubleCheck](https://community.expensify.com/discussion/5738/deep-dive-how-does-concierge-receipt-audit-work) features, your employees shouldn't need to do anything more than swipe their Expensify Card or take a photo of their receipt. + +Expenses with violations will stay behind for the employee to fix, while expenses that are “in-policy” will move into an approver’s queue to mitigate any potential for delays. Scheduled Submit will ensure all expenses are submitted automatically for approval. + +![Scheduled submit](https://help.expensify.com/assets/images/playbook-scheduled-submit.png){:width="100%"} + +> _We spent twice as much time and effort on expenses without getting nearly as accurate of results as with Expensify._ +> +> Kevin Valuska +> AP/AR at Road Trippers + +### Step 8: Connect your business bank account (US only) +If you’re located in the US, you can utilize Expensify’s payment processing and reimbursement features. + +*Note:* Before you begin, you’ll need the following to validate your business bank account: + +1. Your bank account credentials +2. A form of ID (a driver’s license or passport) +3. Your business tax ID number, your business’ address and your website URL + +Let’s walk through the process of linking your business bank account: + +1. Go to *Settings > Account*, and select the *Payments* tab +2. Select *Add Verified Bank Account* +3. From here, we’ll ask you to use your online banking credentials to connect to your bank (Note that this must be the account owner or admin credentials) +- Alternatively, you can go the more manual route by selecting “Connect Manually” +4. Once that’s done, we’ll collect all of the necessary information on your business, such as your legal business name and address +5. We’ll then collect your personal information, and a photo ID to confirm your identity + +You only need to do this once: you are fully set up for not only reimbursing expense reports, but issuing Expensify Cards, collecting customer invoice payments online (if applicable), as well as paying supplier bills online. + +### Step 9: Invite employees and set an approval workflow +*Select an Approval Mode* +We recommend you select *Advanced Approval* as your Approval Mode to set up a middle-management layer of a approval. If you have a single layer of approval, we recommend selecting [Submit & Approve](https://community.expensify.com/discussion/5643/deep-dive-submit-and-approve). But if *Advanced Approval* if your jam, keep reading! + +*Import your employees in bulk via CSV* +Given the amount of employees you have, it’s best you import employees in bulk via CSV. You can learn more about using a CSV file to bulk upload employees with *Advanced Approval [here](https://community.expensify.com/discussion/5735/deep-dive-the-ins-and-outs-of-advanced-approval)* + +![Bulk import your employees](https://help.expensify.com/assets/images/playbook-impoort-employees.png){:width="100%"} + +*Manually Approve All Reports* +In most cases, at this stage, approvers prefer to review all expenses for a few reasons. 1) We want to make sure expense coding is accurate, and 2) We want to make sure there are no bad actors before we export transactions to our accounting system. + +In this case we recommend setting *Manually approve all expenses over: $0* + +### Step 10: Configure Auto-Approval +Knowing you have all the control you need to review reports, we recommend configuring auto-approval for *all reports*. Why? Because you’ve already put reports through an entire approval workflow, and manually triggering reimbursement is an unnecessary action at this stage. + +1. Navigate to *Settings > Policies > Group > [Policy Name] > Reimbursement* +2. Set your *Manual Reimbursement threshold to $20,0000* + +### Step 11: Enable Domains and set up your corporate card feed for employees +Expensify is optimized to work with corporate cards from all banks – or even better, use our own perfectly integrated *[Expensify Card](https://use.expensify.com/company-credit-card)*. The first step for connecting to any bank you use for corporate cards, and the Expensify Card is to validate your company’s domain in Domain settings. + +To do this: + +- Click *Settings* +- Then select *Domains* + +#### If you have an existing corporate card +Expensify supports direct card feeds from most financial institutions. Setting up a corporate card feed will pull in the transactions from the connected cards on a daily basis. To set this up, do the following: + +1. Go to *Company Cards >* Select your bank + - If you don’t see your financial institution in the list of banks we support, you can review an alternative solution in the Feature Deep Dives section below +2. Next, enter your bank account login credentials. + - To successfully connect to your bank, we’ll need the *master admin (primary) account* login credentials. +3. Next, assign the corporate cards to your employees by selecting the employee’s email address and the corresponding card number from the two drop-down menus under the *Assign a Card* section +4. Set a transaction start date (this is really important to avoid pulling in multiple outdated historical expenses that you don’t want employees to submit) + +![If you have existing corporate cards](https://help.expensify.com/assets/images/playbook-existing-corporate-card.png){:width="100%"} + +As mentioned above, we’ll be able to pull in transactions as they post (daily) and handle receipt matching for you and your employees. One benefit of the Expensify Card for your company is being able to see transactions at the point of purchase which provides you with real-time compliance. We even send users push notifications to SmartScan their receipt when it’s required and generate IRS-compliant e-receipts as a backup wherever applicable. + +#### If you don't have a corporate card, use the Expensify Card (US only) +Expensify provides a corporate card with the following features: + +- Up to 2% cash back (up to 4% in your first 3 months!) +- [SmartLimits](https://community.expensify.com/discussion/4851/deep-dive-what-are-unapproved-expense-limits#latest) to control what each individual cardholder can spend +- A stable, unbreakable real-time connection (third-party bank feeds can run into connectivity issues) +- Receipt compliance - informing notifications (eg. add a receipt!) for users *as soon as the card is swiped* +- A 50% discount on the price of all Expensify plans +- Multiple discounts and savings on a host of partner tech suppliers +- Good Karma - 10% of all card interchange we earn goes directly to the Expensify.org Social Justice Community funds + +The Expensify Card is recommended as the most efficient way to manage your company's spending. + +Here’s how to enable it: + +1. There are *two ways* you can [apply for the Expensify Card](https://community.expensify.com/discussion/4874/how-to-apply-for-the-expensify-card) + - *Via your Inbox* + - *Via Domain Settings* - Go to Settings > Domain > Company Cards > Enable Expensify Card +2. Assign the cards to your employees +3. Set *SmartLimits*: + - *Employees* - We recommend a low limit for most employees, roughly double the size of the maximum daily spend – such as $1000. + - *Execs* - We recommend a higher limit for executives, roughly 10x the limit of a non-executive employee (eg, $10,000). + +Once the Expensify Cards have been assigned, each employee will be prompted to enter their mailing address so they can receive their physical card. In the meantime, a virtual card will be ready to use immediately. + +If you have an accounting system we directly integrate with, check out how we take automation a step further with [Continuous Reconciliation](https://community.expensify.com/discussion/7335/faq-what-is-the-expensify-card-auto-reconciliation-process). We’ll create an Expensify Card clearing and liability account for you. Each time settlement occurs, we’ll take the total amount of your purchases and create a journal entry that credits the settlement account and debits the liability account - saving you hours of manual reconciliation work at the end of your statement period. + +### Step 12: Set up Bill Pay and Invoicing +As a small business, managing bills and invoices can be a complex and time-consuming task. Whether you receive bills from vendors or need to invoice clients, it's important to have a solution that makes the process simple, efficient, and cost-effective. + +Here are some of the key benefits of using Expensify for bill payments and invoicing: +- Flexible payment options: Expensify allows you to pay your bills via ACH, credit card, or check, so you can choose the option that works best for you (US businesses only). +- Free, No Fees: The bill pay and invoicing features come included with every policy and workspace, so you won't need to pay any additional fees. +- Integration with your business bank account: With your business bank account verified, you can easily link your finances to receive payment from customers when invoices are paid. + +Let’s first chat through how Bill Pay works + +1. Have your vendors send their invoices to yourdomain.com@expensify.cash. +- This email address comes with every account, so no need to activate it anywhere. +2. Once the invoicehas been received, we’ll create a bill to pay for your review directly in Expensify +3. At the top of the bill, you’ll notice a Pay button. Once you click that, you’ll see options including ACH, credit/debit card, along with mailing a physical check. + +Similarly, you can send bills directly from Expensify as well. + +1. From the *Reports* tab, select the down arrow next to *New Report* and select *Bill* +2. Next, enter the Supplier’s email address, the Merchant name, the total amount, and the date +3. At this point, you can also upload an attachment to further validate the bill if necessary +4. Click *Submit*, we’ll forward the newly created bill directly to your Supplier. + +![Send bills directly from Expensify](https://help.expensify.com/assets/images/playbook-new-bill.png){:width="100%"} + +Reports, invoices, and bills are largely the same, in theory, just with different rules. As such, creating a customer invoice is just like creating an expense report and even a bill. + +1. From the *Reports* tab, select the down arrow next to *New Report* and select *Invoice*. +2. Add all of the expenses/transactions tied to the Invoice +3. Enter the recipient’s email address, a memo if needed, and a due date for when it needs to get paid, and click *Send* + +You’ll notice it’s a slightly different flow from creating a Bill. Here, you are adding the transactions tied to the Invoice, and establishing a due date for when it needs to get paid. If you need to apply any markups, you can do so from your policy settings under the Invoices tab. Your customers can pay their invoice in Expensify via ACH, or Check, or Credit Card. + +### Step 13: Run monthly, quarterly and annual reporting +At this stage, reporting is important and given that Expensify is the primary point of entry for all employee spend, we make reporting visually appealing and wildly customizable. + +1. Head to the *Expenses* tab on the far left of your left-hand navigation +2. Select the pie chart icon on the right top right corner +3. Configure the filters to your preference + +We recommend reporting: + +- *Monthly* - for spend analysis on your GLs, active projects and department spend +- *Quarterly* - for budget comparison reporting. Pull up your BI tool and compare your active budgets with your spend reporting here in Expensify +- *Annually* - Run annual spend trend reports with month-over-month spend analysis, and prepare yourself for the upcoming fiscal year. + +![Expenses!](https://help.expensify.com/assets/images/playbook-expenses.png){:width="100%"} + +### Step 14: Set your Subscription Size and Add a Payment card +Our pricing model is unique in the sense that you are in full control of your billing. Meaning, you have the ability to set a minimum number of employees you know will be active each month and you can choose which level of commitment fits best. We recommend setting your subscription to *Annual* to get an additional 50% off on your monthly Expensify bill. In the end, you've spent enough time getting your company fully set up with Expensify, and you've seen how well it supports you and your employees. Committing annually just makes sense. + +To set your subscription, head to: + +1. Settings > Policies +2. Select *Group* +3. Scroll down to *Subscription* +4. Select *Annual Subscription* +5. Enter the number of employees you know will be active each month +6. Enable *Auto-Renew* and *Auto-Increase Subscription Size* + +Now that we’ve gone through all of the steps for setting up your account, let’s make it official so there are no interruptions in service as your employees begin using Expensify. We handle payments for our service via a paymentcard, and to add one: + +1. Go to *Account > Settings > Payments* +2. Select *Add Payment Card* +3. Enter your name, card number, postal code, expiration and CVV +4. Click *Accept Terms* + +## You’re all set! +Congrats, you are all set up! If you need any assistance with anything mentioned above or would like to understand other features available in Expensify, reach out to your Setup Specialist directly in *[new.expensify.com](https://new.expensify.com)*. Don’t have one yet? Create a Control Policy, and we’ll automatically assign a dedicated Setup Specialist to you. diff --git a/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-US-Based-Bootstrapped-Startups.md b/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-US-Based-Bootstrapped-Startups.md new file mode 100644 index 000000000000..86c6a583c758 --- /dev/null +++ b/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-US-Based-Bootstrapped-Startups.md @@ -0,0 +1,90 @@ +--- +title: Expensify Playbook for US-Based Bootstrapped Startups +description: Best practices for how to deploy Expensify for your business +--- + +This playbook details best practices on how Bootstrapped Startups with less than 5 employees can use Expensify to prioritize product development while capturing business-related receipts for future reimbursement. +- See our *[Playbook for VC-Backed Startups](https://help.expensify.com/articles/playbooks/Expensify-Playbook-for-US-based-VC-Backed-Startups)* if you have taken venture capital investment and are more concerned with prioritizing top-line revenue growth than achieving near-term profitability +- See our *[Playbook for Small to Medium-Sized Businesses](https://help.expensify.com/articles/playbooks/Expensify-Playbook-for-Small-to-Medium-Sized-Businesses)* if you are more concerned with maintaining profitability than growing top-line revenue. + +# Who you are +As a bootstrapped startup, you have surrounded yourself with a small handful of people you trust, and are focused on developing your concept to officially launch and possibly take to outside investors. You are likely running your business with your own money, and possibly a small amount of funding from friends and family. You are either paying yourself a little, or not at all, but at this stage, the company isn’t profitable. And for now, you are capturing receipts so that you can reimburse yourself for startup costs when you either take on investment or start to turn a profit. + +# Step-by-step instructions for setting up Expensify +This playbook is built based on best practices we’ve developed after processing expenses for tens of thousands of companies around the world. As such, use this playbook as your starting point, knowing that you can customize Expensify to suit your business needs. Every company is different, and we’re always one chat away with any questions you may have. + +## Step 1: Create your Expensify account +If you don't already have one, go to *[new.expensify.com](https://new.expensify.com)* and sign up for an account with your business email address. + +## Step 2: Create a Free Plan Workspace +There are three plans (Free, Collect, and Control), but for your needs, we recommend the *Free Plan* for the following reasons: + +- You are looking to capture receipts, but you don’t need to submit expenses to anyone for approval or reimbursement +- You are a small, highly collaborative group and project-oriented Chat functionality can help you stay organized +- When your business produces enough cash that you can pay yourself, you might want to reimburse yourself for the expenses you’ve captured + +To create your Free Plan Workspace: + +1. Go to *[new.expensify.com](https://new.expensify.com)* +2. Select *New Workspace* +3. Select the avatar in the top left corner +4. Click on the workspace name and rename it +5. Express yourself by adding an avatar image, preferably an image of yourself + +The Free Plan also gives you direct access to lightning-fast 24/7 support via Concierge. Within *[new.expensify.com](https://new.expensify.com/concierge)*, you can start a direct message (DM) with Concierge anytime. + +## Step 3: Invite Your Team +As a bootstrapped startup, you communicate with your team all day. Similarly, if you are a co-founder, you will have multiple people that will need to capture receipts for your project. + +1. Click on your avatar +2. Select *Workspaces* +3. Click on your workspace +4. Select *Members* +5. Click *Invite*, and enter each team member’s email address + +By inviting your team, all members of your team will have access to unlimited receipt capture via SmartScan, and you’ll all have access to our free chat tool, which makes it easy to chat about your project and the expenses you’ve captured to help accelerate your project. + +## Step 4: Link Your Business Bank Account (Optional) +If you have a business bank account at this stage, congrats! You might be better suited to check out our Seed Stage playbook. If you are located in the US, you can unlock many great features by linking your US-based business bank account such as paying bills via Expensify, along with the Expensify Card. + +Here’s how to set it up: + +1. Click on your *avatar* +2. Select *Workspaces* +3. Click on your workspace name +4. At the bottom, select *Bank account* +5. Select your bank and enter your online login credentials + +Once this is done, you are all set to begin the process of enabling the Expensify Card. Not just for you, but if you have a co-founder, you can also issue them a card. + +## Step 5: Get the Expensify Card +If you linked your business bank account in the previous step, you are immediately eligible for the Expensify Card. The Expensify Card is included with your Free Plan workspace, it earns you up to 2% cash back (up to 4% in your first 3 months!), and they are all stored in your team’s workspace. It’s easy to apply and it takes minutes! + +Here’s how to enable the Expensify Card: + +1. Click on your *avatar* +2. Select *Workspaces* +3. Click on your workspace +4. Select *Cards* +5. Next, you’ll be redirected to expensify.com +6. Set a SmartLimit > $0 +7. We’ll also ask you for your mailing address to send you a physical Expensify Card + +You’ll be issued a virtual card that you can use immediately, however, in 1-3 business days your Expensify Card will arrive. You can use the Expensify Card anywhere Visa is accepted. + +## Step 6: View and Pay Bills +Whether it’s online services or vendors you work with to grow your project, you likely have bills to pay. And now that you have your business bank account linked, you can easily pay bills directly from Expensify. Seeing as these are project-related expenses, we’ll capture them for you for future reference directly within your project’s workspace. + +There’s no actual setup required. Your free plan comes with a _yourdomain.com@expensify.com_ email address. Just have all of your vendors send bills to that email address. + +To view and pay bills: + +1. Click on your *avatar* +2. Select *Workspaces* +3. Click on your workspace +4. Select *Bills* + +When you have bills to pay you can click *View all bills* under the *Manage your bills* box and we’ll keep a neatly organized list of all of the bills you can pay via ACH directly from your Expensify account. + +# You’re all set! +Congrats, you are all set up! If you need any assistance with anything mentioned above, reach out to either your Concierge directly in *[new.expensify.com](https://new.expensify.com/concierge)*, or email concierge@expensify.com. Create a Collect or Control Policy, and we’ll automatically assign a dedicated Setup Specialist to you. diff --git a/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-US-Based-VC-Backed-Startups.md b/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-US-Based-VC-Backed-Startups.md new file mode 100644 index 000000000000..501d2f1538ef --- /dev/null +++ b/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-US-Based-VC-Backed-Startups.md @@ -0,0 +1,208 @@ +--- +title: Expensify Playbook for US-Based VC-Backed Startups +description: Best practices for how to deploy Expensify for your business +--- +This playbook details best practices on how Seed to Series A startups with under 100 employees can use Expensify to prioritize top-line revenue growth while managing spend responsibly. + +- See our Playbook for Bootstrapped Businesses if you haven't taken any VC money yet. +- See our Playbook for Small Businesses if you are more concerned with maintaining profitability than growing top-line revenue. [Coming soon…] +- See our Playbook for Midsize Businesses if you are series B or beyond, or have more than 100 employees. [Coming soon…] + +# Who you are +As a VC-backed business focused on growth and efficiency, you are looking for a product that puts smart automation in your hands. You prioritize top-line revenue growth over cash conservation, understanding that you’ll need to spend in order to grow. As a result, you want to enable your employees by putting spending power in their hands responsibly so there are appropriate compliance controls in place that scale with the business growth. Not only that, you want to decrease the amount of time you spend at the end of each month reimbursing employees, reconciling spend, and closing your books. + +# Step-by-step instructions for setting up Expensify +This playbook is built based on best practices we’ve developed after processing expenses for tens of thousands of companies around the world. As such, use this playbook as your starting point, knowing that you can customize Expensify to suit your business needs. Every company is different, and we’re always one chat away with any questions you may have. + +## Step 1: Create your Expensify account +If you don't already have one, go to *[new.expensify.com](https://new.expensify.com)* and sign up for an account with your work email address. The account is free so don’t worry about the cost at this stage. + +## Step 2: Create a Control Policy +There are three policy types, but for your needs we recommend the Control Policy for the following reasons: + +- You can cap spend on certain expense types, and set compliance controls so Expensify’s built-in Concierge Audit Tracking can detect violations on your behalf +- As a growing business with VC-funding, the Control plan will scale with you as your team grows and you start to introduce more sophisticated approval workflows + +To create your Control Policy: + +1. Go to *Settings > Policies* +2. Select *Group* and click the button that says *New Policy* +3. Click *Select* under Control + +The Control plan also gives you access to a dedicated Setup Specialist. You can find yours by looking at your policy's #admins room at *[new.expensify.com](https://new.expensify.com)*, and chatting with them there. The Control plan is bundled with the Expensify Card is $9 per user per month when you commit annually, which is a 75% discount off our standard unbundled price point. The Control plan also gives you access to a dedicated Setup Specialist. You can find yours by looking at your policy's *#admins* room in *[new.expensify.com](https://new.expensify.com)*, and chat with them there. + +## Step 3: Connect your accounting system +As a VC-backed company, your investors will want to see that your books are managed properly. That means making sure that: + +- Every purchase is categorized into the correct account in your chart of accounts +- Every expense is accounted for and added to your accounting system + +You do this by synchronizing Expensify and your accounting package as follows: + +1. Click *Settings > Policies* +2. Navigate to the *Connections* tab +3. Select your accounting system + - If you don’t see your accounting solution in the list of integrations we support, you can review an alternative solution in the Feature Deep Dives section below. +4. Follow the prompts to connect your accounting package + - Detailed instructions on connecting your accounting package are linked on the Connections page +5. Once connected, your categories will sync, and you’re ready to set Category Rules + +_“Expensify syncs seamlessly with QuickBooks, supports our web-based, paperless workflow, and offers internal controls, so it was the natural choice.”_ + _- Laura Redmond, CEO of Redmond Accounting_ + +## Step 4: Set up category rules +[Category rules](https://community.expensify.com/discussion/4638/how-to-enable-category-specific-rules-and-descriptions) are how you provide employees hints and requirements to make sure purchases stay within reasonable ranges and are documented appropriately for approval. For your company size and stage, we recommend the following: + +1. Click *Settings > Policies* +2. Navigate to the *Categories* tab where you’ll see all the categories you just imported from your accounting package +3. To set a rule for a specific category, click *“Edit Rules”* +4. The Edit Rules section will provide several expense category rules that tie to specific general ledger categories. While the individual rules might change slightly from business to business, and the exact category name will depend on your specific chart of accounts, we recommend these settings for VC backed startups: + - Set a $75 daily limit on meals and entertainment purchases + - Though we recommend [Expensify Guaranteed eReceipts](https://community.expensify.com/discussion/5542/deep-dive-what-are-ereceipts) for most purchases, for large purchases or those in categories most often associated with fraud, we recommend scanned receipts for extra protection: + - For any purchase over $1000 + - For all lodging purchases, regardless of size + - For any meal over $50/person + - For all office supplies + - For all software purchases + - For all airfare purchases + - Require manual explanations for certain high risk categories: + - For airfare expenses a description of the expense mandatory for the employee to include the purpose of the travel + - Require a description for all rideshare and taxi expenses, ensuring employees are listing a purpose for the expense + +Setting up these category rules allows you to concentrate on growth versus needing to micromanage your employees' spending. +## Step 5: Set up scheduled submit +For an efficiency-focused company, we recommend setting up [Scheduled Submit](https://community.expensify.com/discussion/4476/how-to-enable-scheduled-submit-for-a-group-policy) on a Daily frequency: + +1. Click *Settings > Policies* +2. From here, select your group Control policy +3. Within your policy settings, select the *Reports* tab +4. You’ll notice *Scheduled Submit* is located directly under *Report Basics* +5. Choose *Daily* + +Between Expensify's SmartScan technology, direct corporate card feed import, automatic categorization, and [DoubleCheck](https://community.expensify.com/discussion/5738/deep-dive-how-does-concierge-receipt-audit-work) features, your employees shouldn't need to do anything more than swipe their Expensify Card or scan their receipt. + +Scheduled Submit will ensure all expenses are submitted automatically. Any expenses that do not fall within the rules you’ve set up for your policy will be escalated to you for manual review. + +_“Our employees just SmartScan a receipt as soon as they receive it, and regardless of what currency it's in, we process the expense and issue reimbursement automatically.”_ +_- Amina Mobasher, Accountant at Ideo.org_ + +## Step 6: Connect your business bank account +If you’re located in the US, you can utilize Expensify’s payment processing and reimbursement features. + +*Note:* Before you begin, you’ll need the following to validate your business bank account: + +- Your bank account credentials +- A form of ID (a driver’s license or passport) +- Your business tax ID number, your business’ address and your website URL + +Let’s walk through the process of linking your business bank account: + +1. Go to *Settings > Account*, and select the *Payments* tab +2. Select *Add Verified Bank Account* +3. From here, we’ll ask you to use your online banking credentials to connect to your bank + - Alternatively, you can go the more manual route by selecting “Connect Manually” +4. Once that’s done, we’ll collect all of the necessary information on your business, such as your legal business name and address +5. We’ll then collect your personal information, and a photo ID to confirm your identity + +You only need to do this once. You are fully set up for not only reimbursing expense reports, but issuing Expensify Cards, collecting invoice payments online, as well as paying bills online. + +## Step 7: Invite employees +Next, you’ll want to invite your employees to the company policy you created. You can invite employees under *Settings > Policies > Policy Name > People*. From there, you can add employees one of three ways: + +- [Unique Policy Link](https://community.expensify.com/discussion/4643/how-to-invite-people-to-your-policy-using-a-join-link) - Each policy has a unique policy invite link, which is located at the top of the People tab in your policy settings. Simply share that link with anyone you’d like to add to your policy. +- [Manually](https://community.expensify.com/discussion/4975/how-to-invite-users-to-your-policy-manually-or-in-bulk/p1?new=1) - Enter employee email addresses manually by clicking the green Invite button in the People tab of your policy +- [Google SSO](https://community.expensify.com/discussion/4774/how-to-enable-google-apps-sso-with-your-expensify-group-policy) - Or, if you have a Google Workspace configured, you can synchronize your policy's people list to match your Google Workspace employee list. + +In the next section, we’ll go through how to configure approval routing but it’s important to remember that you’ll always have these 3 options to utilize, specifically the unique policy link and manual invites as your team continues to grow. + +## Step 8: Set up an approval workflow +Now, let’s set up some approval rules for your business as well as the ideal approval workflow that employee reports will follow after report submission: + +1. Go to *Settings > Policies*, and select the *People* tab. +2. From there, select [Submit & Approve](https://community.expensify.com/discussion/5643/deep-dive-submit-and-approve) - this will automatically add you as the approver, which ensures that any expenses that fall outside of the rules you set for your policy are brought to your attention. + - *Note*: If you are over 50 employees, please ask your Guide about the benefits of setting up an Advanced Approval workflow. +3. Next, enable manual approval for *expenses over $1000*. + - *Note*: We do not recommend configuring random report auditing for companies of your stage and scale. +4. Next, enable *Workflow Enforcement*. + - This ensures that employees are required to submit to you and not to someone else. +5. Disable *Prevent Self-Approval*. This is a more powerful feature recommended for companies with advanced compliance requirements, but generally isn't recommended for a company of your scale. + +Thanks to our [Concierge Receipt audit technology](https://community.expensify.com/discussion/5738/deep-dive-how-does-concierge-receipt-audit-work), once you set up an approval workflow, most expenses will be audited automatically and won’t require manual review. Your time is valuable, so you should focus it on reviewing only the expenses that fall outside of your policy’s rules. + +## Step 9: Set up your corporate card and assign cards to employees +Expensify is optimized to work with corporate cards from all banks – or even better, use our own perfectly integrated Expensify Card. + +### If you have an existing corporate card +Expensify supports direct card feeds from most financial institutions. Setting up a corporate card feed will pull in the transactions from the connected cards on a daily basis. To set this up, do the following: + +1. Go to *Settings > Domains > Company Cards >* Select your bank + - If you don’t see your financial institution in the list of banks we support, you can review an alternative solution in the Feature Deep Dives section below +2. Next, enter your bank account login credentials. + - To successfully connect to your bank, we’ll need the *master admin (primary) account* login credentials. +3. Next, assign the corporate cards to your employees by selecting the employee’s email address and the corresponding card number from the two drop-down menus under the *Assign a Card* section +4. Set a transaction start date + - If you don’t have a backlog of transactions you’d like to account for, feel free to skip this step. + +As mentioned above, we’ll be able to pull in transactions as they post (daily) and handle receipt matching for you and your employees. However, with the Expensify Card, we’re able to bring in transactions at the point of sale which provides you with real-time compliance. Next, let’s dive into how to set up the Expensify Card and the benefits of using the Expensify Card. + +### If you don't have a corporate card, use the Expensify Card +Expensify provides a corporate card with the following features: + +- Up to 2% cash back (up to 4% in your first 3 months!) +- [SmartLimits](https://community.expensify.com/discussion/4851/deep-dive-what-are-unapproved-expense-limits#latest) +- A stable, unbreakable connection (third-party bank feeds can run into connectivity issues) + +The Expensify Card is recommended as the most efficient way to manage your company's spending. + +Here’s how to enable it: + +1. There are *two ways* you can [apply for the Expensify Card](https://community.expensify.com/discussion/4874/how-to-apply-for-the-expensify-card) + - *Via your Inbox* + - *Via Domain Settings* - Go to Settings > Domain > Company Cards > Enable Expensify Card +2. Assign the cards to your employees +3. Set *SmartLimits*: + - *Employees* - We recommend a low limit for most employees, roughly double the size of the maximum daily spend – such as $1000. + - *Execs* - We recommend a higher limit for executives, roughly 10x the limit of a non-executive employee (eg, $10,000). + +Once the Expensify Cards have been assigned, each employee will be prompted to enter their mailing address so they can receive their physical card. In the meantime, a virtual card will be ready to use immediately. + +If you have an accounting system we directly integrate with, check out how we take automation a step further with [Auto-Reconciliation](https://community.expensify.com/discussion/7335/faq-what-is-the-expensify-card-auto-reconciliation-process). We’ll create an Expensify Card clearing and liability account for you. Each time settlement occurs, we’ll take the total amount of your purchases and create a journal entry that credits the settlement account and debits the liability account - saving you hours of manual reconciliation work at the end of your statement period. + +_“Moving from our other bank and getting Expensify cards into the hands of employees was super easy. I also love how simple it is to adjust credit limits and the auto reconciliation with the daily settlement.”_ +_- Robin Gresham, Senior Accounting Systems Manager at SunCommon_ + +## Step 10: Set up Bill Pay and Invoicing +As a VC-backed startup, you might have vendors you work with that send you bills. And in most cases, you probably use some third party to pay those bills if you aren’t cutting checks the old fashioned way. Similarly, you probably have clients you invoice from time to time. As an all-in-one solution, we’re here to make bill payments and invoicing easy, and every policy and workspace comes with bill pay and invoicing - at no additional cost. Since you have your business bank account verified, you can either pay your bills via ACH. Alternatively, you can pay via credit card or by check. + +Let’s first chat through how Bill Pay works + +1. Have your vendors submit bills to domain.com@expensify.cash. + - This email address comes with every account, so no need to activate it anywhere. +2. Once the bill has been received, we’ll create the bill for your review directly in Expensify +3. At the top of the bill/invoice, you’ll notice a Pay button. Once you click that, you’ll see options including ACH, credit/debit card, along with mailing a physical check. + +Similarly, you can send bills directly from Expensify as well. + +1. From the *Reports* tab, select the down arrow next to *New Report* and select *Bill* +2. Next, enter in the Supplier’s email address, the Merchant name, total amount and date +3. At this point, you can also enter in an attachment to further validate the bill if necessary +4. Click *Submit*, we’ll forward the newly created bill directly to your Supplier. + +Reports, invoices and bills - they are largely the same in theory, just with different rules. As such, creating an invoice is just like creating an expense report and even a Bill. + +1. From the *Reports* tab, select the down arrow next to *New Report* and select *Invoice*. +2. Add all of the expenses/transactions tied to the Invoice +3. Enter the recipient’s email address, a memo if needed, and a due date for when it needs to get paid, and click *Send* + +You’ll notice it’s a slightly different flow from creating a Bill. Here, you are adding the transactions tied to the Invoice, and establishing a due date for when it needs to get paid. If you need to apply any markups, you can do so from your policy settings under the Invoices tab. + +## Step 11: Add a billing card +Now that we’ve gone through all of the steps for setting up your account, let’s make it official so there are no interruptions in service as your employees begin using Expensify. We handle billing via a billing card, and to add one: + +1. Go to *Account > Settings > Payments* +2. Select *Add Payment Card* +3. Enter your name, card number, postal code, expiration and CVV +4. Click *Accept Terms* + +# You’re all set! +Congrats, you are all set up! If you need any assistance with anything mentioned above, reach out to either your Setup Specialist or your Account Manager directly in *[new.expensify.com](https://new.expensify.com)*. Don’t have one yet? Create a Control Policy, and we’ll automatically assign a dedicated Setup Specialist to you. diff --git a/docs/articles/expensify-classic/getting-started/tips-and-tricks.md b/docs/articles/expensify-classic/getting-started/tips-and-tricks.md new file mode 100644 index 000000000000..d85c7f3a0cb9 --- /dev/null +++ b/docs/articles/expensify-classic/getting-started/tips-and-tricks.md @@ -0,0 +1,5 @@ +--- +title: Tips and Tricks +description: Tips and Tricks +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/getting-started/tips-and-tricks/Enable-Location-Access-On-Web.md b/docs/articles/expensify-classic/getting-started/tips-and-tricks/Enable-Location-Access-On-Web.md new file mode 100644 index 000000000000..649212b00f7b --- /dev/null +++ b/docs/articles/expensify-classic/getting-started/tips-and-tricks/Enable-Location-Access-On-Web.md @@ -0,0 +1,55 @@ +--- +title: Enable Location Access on Web +description: How to enable location access for Expensify websites on your browser +--- + + +# About + +If you'd like to use features that rely on your current location you will need to enable location permissions for Expensify. You can find instructions for how to enable location settings on the three most common web browsers below. If your browser is not in the list then please do a web search for your browser and "enable location settings". + +# How-to + + +### Chrome +1. Open Chrome +2. At the top right, click the three-dot Menu > Settings +3. Click "Privacy and Security" and then "Site Settings" +4. Click Location +5. Check the "Not allowed to see your location" list to make sure expensify.com and new.expensify.com are not listed. If they are, click the delete icon next to them to allow location access + +[Chrome help page](https://support.google.com/chrome/answer/142065) + +### Firefox + +1. Open Firefox +2. In the URL bar enter "about:preferences" +3. On the left hand side select "Privacy & Security" +4. Scroll down to Permissions +5. Click on Settings next to Location +6. If location access is blocked for expensify.com or new.expensify.com, you can update it here to allow access + +[Firefox help page](https://support.mozilla.org/en-US/kb/permissions-manager-give-ability-store-passwords-set-cookies-more) + +### Safari +1. In the top menu bar click Safari +2. Then select Settings > Websites +3. Click Location on the left hand side +4. If expensify.com or new.expensify.com have "Deny" set as their access, update it to "Ask" or "Allow" + +Ask: The site must ask if it can use your location. +Deny: The site can’t use your location. +Allow: The site can always use your location. + +[Safari help page](https://support.apple.com/guide/safari/websites-ibrwe2159f50/mac) \ No newline at end of file diff --git a/docs/articles/expensify-classic/integrations/accounting-integrations/Bill-dot-com.md b/docs/articles/expensify-classic/integrations/accounting-integrations/Bill-dot-com.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/accounting-integrations/Bill-dot-com.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/accounting-integrations/FinancalForce.md b/docs/articles/expensify-classic/integrations/accounting-integrations/FinancalForce.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/accounting-integrations/FinancalForce.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/accounting-integrations/NetSuite.md b/docs/articles/expensify-classic/integrations/accounting-integrations/NetSuite.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/accounting-integrations/NetSuite.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/accounting-integrations/QuickBooks-Desktop.md b/docs/articles/expensify-classic/integrations/accounting-integrations/QuickBooks-Desktop.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/accounting-integrations/QuickBooks-Desktop.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/accounting-integrations/QuickBooks-Online.md b/docs/articles/expensify-classic/integrations/accounting-integrations/QuickBooks-Online.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/accounting-integrations/QuickBooks-Online.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/accounting-integrations/Sage-Intacct.md b/docs/articles/expensify-classic/integrations/accounting-integrations/Sage-Intacct.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/accounting-integrations/Sage-Intacct.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/accounting-integrations/Xero.md b/docs/articles/expensify-classic/integrations/accounting-integrations/Xero.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/accounting-integrations/Xero.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/hr-integrations/ADP.md b/docs/articles/expensify-classic/integrations/hr-integrations/ADP.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/hr-integrations/ADP.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/hr-integrations/Greenhouse.md b/docs/articles/expensify-classic/integrations/hr-integrations/Greenhouse.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/hr-integrations/Greenhouse.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/hr-integrations/Gusto.md b/docs/articles/expensify-classic/integrations/hr-integrations/Gusto.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/hr-integrations/Gusto.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/hr-integrations/QuickBooks-Time.md b/docs/articles/expensify-classic/integrations/hr-integrations/QuickBooks-Time.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/hr-integrations/QuickBooks-Time.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/hr-integrations/Rippling.md b/docs/articles/expensify-classic/integrations/hr-integrations/Rippling.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/hr-integrations/Rippling.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/hr-integrations/Workday.md b/docs/articles/expensify-classic/integrations/hr-integrations/Workday.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/hr-integrations/Workday.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/hr-integrations/Zenefits.md b/docs/articles/expensify-classic/integrations/hr-integrations/Zenefits.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/hr-integrations/Zenefits.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/other-integrations/Google-Apps-SSO.md b/docs/articles/expensify-classic/integrations/other-integrations/Google-Apps-SSO.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/other-integrations/Google-Apps-SSO.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/Bolt.md b/docs/articles/expensify-classic/integrations/travel-integrations/Bolt.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/travel-integrations/Bolt.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/Egencia.md b/docs/articles/expensify-classic/integrations/travel-integrations/Egencia.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/travel-integrations/Egencia.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/Global-VaTax.md b/docs/articles/expensify-classic/integrations/travel-integrations/Global-VaTax.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/travel-integrations/Global-VaTax.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/Grab.md b/docs/articles/expensify-classic/integrations/travel-integrations/Grab.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/travel-integrations/Grab.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/Hotel-Tonight.md b/docs/articles/expensify-classic/integrations/travel-integrations/Hotel-Tonight.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/travel-integrations/Hotel-Tonight.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/Kayak.md b/docs/articles/expensify-classic/integrations/travel-integrations/Kayak.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/travel-integrations/Kayak.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/Lyft.md b/docs/articles/expensify-classic/integrations/travel-integrations/Lyft.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/travel-integrations/Lyft.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/TrainLine.md b/docs/articles/expensify-classic/integrations/travel-integrations/TrainLine.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/travel-integrations/TrainLine.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/TravelPerk.md b/docs/articles/expensify-classic/integrations/travel-integrations/TravelPerk.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/travel-integrations/TravelPerk.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/Trip-Actions.md b/docs/articles/expensify-classic/integrations/travel-integrations/Trip-Actions.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/travel-integrations/Trip-Actions.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/TripCatcher.md b/docs/articles/expensify-classic/integrations/travel-integrations/TripCatcher.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/travel-integrations/TripCatcher.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/Uber.md b/docs/articles/expensify-classic/integrations/travel-integrations/Uber.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/travel-integrations/Uber.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/manage-employees-and-report-approvals/Adding-Users.md b/docs/articles/expensify-classic/manage-employees-and-report-approvals/Adding-Users.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/manage-employees-and-report-approvals/Adding-Users.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/manage-employees-and-report-approvals/Approval-Workflows.md b/docs/articles/expensify-classic/manage-employees-and-report-approvals/Approval-Workflows.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/manage-employees-and-report-approvals/Approval-Workflows.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/manage-employees-and-report-approvals/Approving-Reports.md b/docs/articles/expensify-classic/manage-employees-and-report-approvals/Approving-Reports.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/manage-employees-and-report-approvals/Approving-Reports.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/manage-employees-and-report-approvals/User-Roles.md b/docs/articles/expensify-classic/manage-employees-and-report-approvals/User-Roles.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/manage-employees-and-report-approvals/User-Roles.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/manage-employees-and-report-approvals/Vacation-Delegate.md b/docs/articles/expensify-classic/manage-employees-and-report-approvals/Vacation-Delegate.md new file mode 100644 index 000000000000..e10e0fafb77d --- /dev/null +++ b/docs/articles/expensify-classic/manage-employees-and-report-approvals/Vacation-Delegate.md @@ -0,0 +1,8 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! + + +Kayak.md Lyft.md TrainLine.md TravelPerk.md Trip Actions.md TripCatcher.md Uber.md \ No newline at end of file diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/Admins.md b/docs/articles/expensify-classic/policy-and-domain-settings/Admins.md new file mode 100644 index 000000000000..8c1267068d6b --- /dev/null +++ b/docs/articles/expensify-classic/policy-and-domain-settings/Admins.md @@ -0,0 +1,5 @@ +--- +title: Admins +description: Admins +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/Categories.md b/docs/articles/expensify-classic/policy-and-domain-settings/Categories.md new file mode 100644 index 000000000000..00ade2b9d04f --- /dev/null +++ b/docs/articles/expensify-classic/policy-and-domain-settings/Categories.md @@ -0,0 +1,5 @@ +--- +title: Categories +description: Categories +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/Domain-Admins.md b/docs/articles/expensify-classic/policy-and-domain-settings/Domain-Admins.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/policy-and-domain-settings/Domain-Admins.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/Domain-Members.md b/docs/articles/expensify-classic/policy-and-domain-settings/Domain-Members.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/policy-and-domain-settings/Domain-Members.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/Domains-Overview.md b/docs/articles/expensify-classic/policy-and-domain-settings/Domains-Overview.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/policy-and-domain-settings/Domains-Overview.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/Expenses.md b/docs/articles/expensify-classic/policy-and-domain-settings/Expenses.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/policy-and-domain-settings/Expenses.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/Invoicing.md b/docs/articles/expensify-classic/policy-and-domain-settings/Invoicing.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/policy-and-domain-settings/Invoicing.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/Overview.md b/docs/articles/expensify-classic/policy-and-domain-settings/Overview.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/policy-and-domain-settings/Overview.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/Per-Diem.md b/docs/articles/expensify-classic/policy-and-domain-settings/Per-Diem.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/policy-and-domain-settings/Per-Diem.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/Reimbursement.md b/docs/articles/expensify-classic/policy-and-domain-settings/Reimbursement.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/policy-and-domain-settings/Reimbursement.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/Reports.md b/docs/articles/expensify-classic/policy-and-domain-settings/Reports.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/policy-and-domain-settings/Reports.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/SAML.md b/docs/articles/expensify-classic/policy-and-domain-settings/SAML.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/policy-and-domain-settings/SAML.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/Tags.md b/docs/articles/expensify-classic/policy-and-domain-settings/Tags.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/policy-and-domain-settings/Tags.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/Tax.md b/docs/articles/expensify-classic/policy-and-domain-settings/Tax.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/policy-and-domain-settings/Tax.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/Trips.md b/docs/articles/expensify-classic/policy-and-domain-settings/Trips.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/policy-and-domain-settings/Trips.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/send-payments/Pay-Bills.md b/docs/articles/expensify-classic/send-payments/Pay-Bills.md new file mode 100644 index 000000000000..e319196eb4bd --- /dev/null +++ b/docs/articles/expensify-classic/send-payments/Pay-Bills.md @@ -0,0 +1,5 @@ +--- +title: Pay Bills +description: Pay Bills +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/send-payments/Pay-Invoices.md b/docs/articles/expensify-classic/send-payments/Pay-Invoices.md new file mode 100644 index 000000000000..0ea4d28a731a --- /dev/null +++ b/docs/articles/expensify-classic/send-payments/Pay-Invoices.md @@ -0,0 +1,5 @@ +--- +title: Pay Invoices +description: Pay Invoices +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/send-payments/Reimbursing-Reports.md b/docs/articles/expensify-classic/send-payments/Reimbursing-Reports.md new file mode 100644 index 000000000000..6c3309310ba8 --- /dev/null +++ b/docs/articles/expensify-classic/send-payments/Reimbursing-Reports.md @@ -0,0 +1,5 @@ +--- +title: Reimbursing Reports +description: Reimbursing Reports +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/send-payments/Third-Party-Payments.md b/docs/articles/expensify-classic/send-payments/Third-Party-Payments.md new file mode 100644 index 000000000000..4b1166cc9c00 --- /dev/null +++ b/docs/articles/expensify-classic/send-payments/Third-Party-Payments.md @@ -0,0 +1,8 @@ +--- +title: Third Party Payments +description: Third Party Payments +--- +## Resources Coming Soon! + + + \ No newline at end of file From c00f4e48ba7180ee97b29e83599bd604c059ff58 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 20 Sep 2023 20:23:25 +0530 Subject: [PATCH 099/151] add new expensify articles --- .../billing-and-plan-types/The-Free-Plan.md | 62 +++++++++++++ .../get-paid-back/Request-Money.md | 5 + .../getting-started/Expensify-Lounge.md | 66 +++++++++++++ .../getting-started/Referral-Program.md | 53 +++++++++++ .../chat/Everything-About-Chat.md | 85 +++++++++++++++++ .../chat/Expensify-Chat-For-Admins.md | 27 ++++++ ...Expensify-Chat-For-Conference-Attendees.md | 35 +++++++ .../Expensify-Chat-For-Conference-Speakers.md | 39 ++++++++ ...Expensify-Chat-Playbook-For-Conferences.md | 93 +++++++++++++++++++ .../QuickBooks-Online.md | 5 + 10 files changed, 470 insertions(+) create mode 100644 docs/articles/new-expensify/billing-and-plan-types/The-Free-Plan.md create mode 100644 docs/articles/new-expensify/get-paid-back/Request-Money.md create mode 100644 docs/articles/new-expensify/getting-started/Expensify-Lounge.md create mode 100644 docs/articles/new-expensify/getting-started/Referral-Program.md create mode 100644 docs/articles/new-expensify/getting-started/chat/Everything-About-Chat.md create mode 100644 docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Admins.md create mode 100644 docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Conference-Attendees.md create mode 100644 docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Conference-Speakers.md create mode 100644 docs/articles/new-expensify/getting-started/chat/Expensify-Chat-Playbook-For-Conferences.md create mode 100644 docs/articles/new-expensify/integrations/accounting-integrations/QuickBooks-Online.md diff --git a/docs/articles/new-expensify/billing-and-plan-types/The-Free-Plan.md b/docs/articles/new-expensify/billing-and-plan-types/The-Free-Plan.md new file mode 100644 index 000000000000..0a8d6b3493e0 --- /dev/null +++ b/docs/articles/new-expensify/billing-and-plan-types/The-Free-Plan.md @@ -0,0 +1,62 @@ +--- +title: The Free Plan +description: Everything you need to know about Expensify's Free Plan! +--- + + + +# What is the Free Plan? +The free plan is ideal for start-ups and small businesses to manage expenses. With the Free Plan, a workspace admin can set their team up with Expensify Cards, reimburse cash expenses, send invoices, and manage bills, all for free! You will have total visibility and control over all spending associated with your workspace in real time. + +# Features Included with the Free Plan +- Expensify Cards for all employees +- Invoicing +- Bill Pay +- Unlimited receipt scanning for everyone in the company +- Free next-day ACH reimbursements for cash expenses +- Up to 4% [cash back](https://community.expensify.com/discussion/8454/4-cash-back-on-the-expensify-card-is-here-to-stay) +- Free corporate travel booking + +# Setting Up the Free Plan +- Navigate to new.expensify.com, enter your company email address, and set a password +- Click the **green “+”** button and select **_New workspace_** + +Once you’ve created your Workspace, you will receive a message from Concierge encouraging you to chat with your Setup Specialist. Click the link in the message, and your designated Setup Specialist will guide you through how to configure your company setup. + +Once you’ve completed your company setup, you should have completed the following tasks: + +- Connected a business bank account (Settings menu > Click **_Bank account_** and follow the prompts). +- Invited members to the workspace +- Assigned Expensify Cards + +# Inviting Members to the Free Plan: +- Navigate to the Settings Menu and click **_Members_** to invite your team. You can invite employees one at a time, or you can invite multiple users by listing out their email addresses separated by a comma +- To use the Expensify Card, you must invite them to your workspace via your company email address (i.e., admin@companyemail.com and NOT admin@gmail.com). + +# Managing the Free Plan +To access your workspace settings, click your profile icon and then on your workspace name. + +This settings menu allows you to manage your workspace members, issue additional Expensify Cards, and utilize this plan’s various bill pay and payment options. + +# FAQs +## Do I need a business bank account to use the Free Plan? + +You will need a US business checking account if you want to enable the Expensify Card and set up direct ACH reimbursement. +You will need to take a few steps to verify your business bank account and connect it to Expensify. You will also need to set aside some time and have your ID ready. +If you're not in the US, you can still use the Free Plan, but the Expensify Card and direct reimbursement will not be available. + +## Can my workspace have more than one Admin? + +The Expensify Workplace only allows for one admin (the workspace creator). + +## Scheduled Submit is set to weekly on the Free Plan. Can I change this? + +No, expense reports on the Free Plan submit weekly, and there is no way to customize approval settings. + +## With the Free Plan, can I add my own categories? + +Categories are standardized on the Free Plan and can’t be edited. Custom categories and tags or accounting system integration are available on a paid plan. + +## With the Free Plan, can I export reports using a custom format? + +The Free Plan offers standard report export formats. You'll need to upgrade to a paid plan to create a custom export format. \ No newline at end of file diff --git a/docs/articles/new-expensify/get-paid-back/Request-Money.md b/docs/articles/new-expensify/get-paid-back/Request-Money.md new file mode 100644 index 000000000000..55a3f3c8172e --- /dev/null +++ b/docs/articles/new-expensify/get-paid-back/Request-Money.md @@ -0,0 +1,5 @@ +--- +title: Request Money +description: Request Money +--- +## Resources Coming Soon! diff --git a/docs/articles/new-expensify/getting-started/Expensify-Lounge.md b/docs/articles/new-expensify/getting-started/Expensify-Lounge.md new file mode 100644 index 000000000000..01a2d7a9e250 --- /dev/null +++ b/docs/articles/new-expensify/getting-started/Expensify-Lounge.md @@ -0,0 +1,66 @@ +--- +title: Welcome to the Expensify Lounge! +description: How to get the most out of the Expensify Lounge. +--- + + +# What is the Expensify Lounge? +The Expensify Lounge is a place where people go to Get Shit Done. It's a beautiful environment with great coffee and a group of people to collaborate with. Check out this guide on how to best utilize the Expensify Lounge! + +# The Two Rules +### Rule #1 - Get Shit Done + +The Lounge is a space for people to get work done. It is optimized to be the perfect environment for you to focus on your work, collaborate with others, and advance your most wild and creative ideas. To make this a reality, we ask our members to keep the following in mind: + +- **#focus** - Use the space for how it was designed and do not distract from others' focus. The space is beautiful, social, and collaborative, but it was created to help our members work effectively. +- **#urgency** - Working remotely is great, but there's nothing like real-time collaboration with your colleagues. Use the lounge to meet with co-workers IRL to continue the progress on whatever it is you're working on. +- **#results** - Don't mistake time for effort or effort for output. Upon arrival, visualize what you want to accomplish, and don't leave until it's done. + +## Rule #2 - Don’t Ruin it for Everyone Else + +We want this place to be incredible, innovative, and always elvoving. To achieve that, we have some general guidelines: + +- **#writeitdown** - If you can help others learn from you, do so. Write a blog post, a document, or a post in Expensify Chat to share with others. This includes making the Expensify Lounge a better space. Feel free to write down any improvements so we can make it better. +- **#showup** - If you are in the lounge, be fully present. Meet others, and collaborate in social rooms. The point is to build a community of people who are focused on getting shit done; you’ll get out what you put in. +- **#oneteam** - Providing an inclusive community is our priority, and we do not tolerate any form of discrimination. Aim to go out of your way to include people who want to be included. +- **#nocreeps** - Do not make people feel uncomfortable with your words or actions. If you are made to feel uncomfortable or notice this happening to someone else, you can use the escalation process outlined in the FAQ section. + +# How to Use the Expensify Lounge +Keeping those two rules in mind, below is a guide on how our members can get the most out of the lounge. + +### Rule #1 - Getting Shit Done +- **Order drinks from Concierge** - [Write Concierge here](https://new.expensify.com/concierge) to ask lounge questions or order beverages. Concierge will bring your order directly to you! +- **Using an office** - Offices are first come, first serve. If an office is open, feel free to use it! Please keep office use to under an hour. We currently do not allow reserving offices. +- **Lounge hours** - The lounge will be open from 8am-6pm PT, Monday through Friday and closed on some major holidays. You can review our Google Maps profile to check our holiday hours. +- **Make the lounge better** - Make any suggestions to improve the lounge experience in [#announce - Expensify Lounge](https://new.expensify.com/r/8292963527436014). + +## Rule #2 - Not Ruining it for Everyone Else +- **Offices are for calls** - Please do not occupy an office unless you have a call or collaborative meeting happening, and don't stay in an office for longer than an hour. +- **Respect other people** - Please do not be too loud or distracting while others are trying to work. While collaborating in Expensify Chat, be respectful of others’ viewpoints and keep a positive environment. +- **Stay home if you’re sick** - If you feel sick, please do not visit the lounge, or consider wearing a mask in public areas. +- **If you see something, say something** - If you are made to feel uncomfortable or witness others being made uncomfortable, let Concierge know. If this is happening in Expensify Chat, use our moderation tools (outlined below in the FAQ) to apply the applicable level of moderation. + +We’re so happy you are here to live rich, have fun, and save the world with us. Now, go enjoy the Expensify Lounge, and let's Get Shit Done! + +# FAQs + +#### What is Concierge? + +Concierge is our automated system that answers member questions in real-time. Questions regarding the local lounge will be routed directly to the lounge's Concierge. You can send Concierge a message if you have a drink request or general questions. They’ll take care of everything for you! + +#### Who is invited to the Expensify Lounge? + +Everyone is invited to the Expensify Lounge! Whether you're an existing customer, or you're someone looking for a great space to Get Shit Done, we'd love to have you. + +#### How do I escalate something that's making me or someone else uncomfortable? + +If you see something in Expensify Chat that should be escalated, you can use the escalation feature to mark a chat as: +- **Spam or Inconsiderate**: This will send a whisper to the sender of the message warning them of the violation, and the message will have a flag applied to it which will be visible to all users. Concierge will not review these flags. +- **Intimidating or Bullying**: The message will be immediately hidden, and the content will be reviewed by our team. After reviewing the message, and it's confirmed intimidation or bullying, the message will be permanently hidden and we'll communicate the violation to the sender of the message. +- **Harassment or Assault**: The message will be immediately hidden and reviewed by our team. The user will be sent a message to warning them of the violation, and Concierge can block the user if that's deemed necessary. + +If you witness something in-person, please write to Concierge referencing which lounge you are in, and they will escalate the issue appropriately. + +#### Where are other Expensify Lounge locations? + +Right now, we only have the San Francisco Lounge, but be on the lookout for more coming soon! diff --git a/docs/articles/new-expensify/getting-started/Referral-Program.md b/docs/articles/new-expensify/getting-started/Referral-Program.md new file mode 100644 index 000000000000..683e93d0277a --- /dev/null +++ b/docs/articles/new-expensify/getting-started/Referral-Program.md @@ -0,0 +1,53 @@ +--- +title: Expensify Referral Program +description: Send your joining link, submit a receipt or invoice, and we'll pay you if your referral adopts Expensify. +--- + + +# About + +Expensify has grown thanks to our users who love Expensify so much that they tell their friends, colleagues, managers, and fellow business founders to use it, too. + +As a thank you, every time you bring a new user into the platform who directly or indirectly leads to the adoption of a paid annual plan on Expensify, you will earn $250. + +# How to get paid for referring people to Expensify + +1. Submit a report or invoice, or share your referral link with anyone you know who is spending too much time on expenses, or works at a company that could benefit from using Expensify. + +2. You will get $250 for any referred business that commits to an annual subscription, has 2 or more active users, and makes two monthly payments. + +That’s right! You can refer anyone working at any company you know. + +If their company goes on to become an Expensify customer with an annual subscription, and you are the earliest recorded referrer of a user on that company’s paid Expensify Policy, you'll get paid a referral reward. + +The best way to start is to submit any receipt to your manager (you'll get paid back and set yourself up for $250 if they start a subscription: win-win!) + +Referral rewards for the Spring/Summer 2023 campaign will be paid by direct deposit. + +# FAQ + +- **How will I know if I am the first person to refer a company to Expensify?** + +Successful referrers are notified after their referral pays for 2 months of an annual subscription. We will check for the earliest recorded referrer of a user on the policy, and if that is you, then we will let you know. + +- **How will you pay me if I am successful?** + +In the Spring 2023 campaign, Expensify will be paying successful referrers via direct deposit to the Deposit-Only account you have on file. Referral payouts will happen once a month for the duration of the campaign. If you do not have a Deposit-Only account at the time of your referral payout, your deposit will be processed in the next batch. + +Learn how to add a Deposit-Only account [here](https://community.expensify.com/discussion/4641/how-to-add-a-deposit-only-bank-account-both-personal-and-business). + +- **I’m outside of the US, how do I get paid?** + +While our referral payouts are in USD, you will be able to get paid via a Wise Borderless account. Learn more [here](https://community.expensify.com/discussion/5940/how-to-get-reimbursed-outside-the-us-with-wise-for-non-us-employees). + +- **My referral wasn’t counted! How can I appeal?** + +Expensify reserves the right to modify the terms of the referral program at any time, and pays out referral bonuses for eligible companies at its own discretion. + +Please send a message to concierge@expensify.com with the billing owner of the company you have referred and our team will review the referral and get back to you. + +- **Where can I find my referral link?** + +Expensify members who are opted-in for our newsletters will have received an email containing their unique referral link. + +On the mobile app, go to **Settings** > **Invite a Friend** > **Share Invite Link** to retrieve your referral link. diff --git a/docs/articles/new-expensify/getting-started/chat/Everything-About-Chat.md b/docs/articles/new-expensify/getting-started/chat/Everything-About-Chat.md new file mode 100644 index 000000000000..9f73d1c759c2 --- /dev/null +++ b/docs/articles/new-expensify/getting-started/chat/Everything-About-Chat.md @@ -0,0 +1,85 @@ +--- +title: Everything About Chat +description: Everything you need to know about Expensify's Chat Features! +--- + + + +# What is Expensify Chat? +Expensify Chat is an ideal way to collaborate on expenses or payment requests by communicating in real-time with your accountant, clients, employees, or, friends. + +With Expensify Chat, you can start a conversation about that missing receipt your employee forgot to submit or chat about splitting that electric bill with your roommates. Through eChat, you can even request money from your friends after a night out on the town! + +# How to use Chat in Expensify +Download NewExpensify from the [App Store](https://apps.apple.com/us/app/expensify-cash/id1530278510) or [Google Play](https://play.google.com/store/apps/details?id=com.expensify.chat) to use the chat function. You can also access your account at new.expensify.com from your favorite web browser. + +After downloading the app, log into your new.expensify.com account (you’ll use the same login information as your Expensify Classic account). From there, you can customize your profile and start chatting immediately. + + +## Start Chatting +Select **New Chat** to chat one-on-one or **New Group** to start a group chat. +## Workspace Chat Rooms +In addition to 1:1 and group chat, members of a Workspace or Policy will have access to two additional rooms; the #announce and #admins rooms. +All workspace members are added to the #announce room by default. The #announce room lets you share important company announcements and have conversations between workspace members. + +All workspace admins can access the #admins room. Use the #admins room to collaborate with the other admins on your policy, and chat with your dedicated Expensify Onboarding Guide. If you have a subscription of 10 or more users, you're automatically assigned an Account Manager. You can ask for help and collaborate with your Account Manager in this same #admins room. Anytime someone on your team, your dedicated setup specialist, or your dedicated account manager makes any changes to your Workspace settings, that update is logged in the #admins room. +## How to format text + +- To italicize your message, place an underscore on both sides of the text: *text* +- To bold your message, place an asterisk on both sides of the text: **text** +- To strikethrough your message, place a tilde on both sides of the text: ~~text~~ +- To turn your message into code, place a backtick on both sides of the text: `text` +- To turn your text into a blockquote, add an angled bracket (>) in front of the text: + >your text +- To turn your message into a heading, place a number sign (#) in front of the text: +# Heading +- To turn your entire message into code block, place three backticks on both sides of the text: +``` +here's some text +and even more text +``` + +# FAQs +## How do I add more than one person to a chat? +Start by clicking the green chat **+** button and select **New Group**. Search for the people you want to invite and check the circle to the far right. Once you’ve selected everyone you want in the group chat, click the **Create Group** button at the bottom of your screen. + +## Can I add people to an existing Group chat? +Adding people to an existing group chat isn’t possible right now, so you’ll want to make a new group chat instead. + +## Someone I don’t recognize is in my #admins room for my workspace; who is it? +After creating your workspace, you’ll have a dedicated Expensify specialist who will help you onboard and answer your questions. You can chat with them directly in the #admins room or request a call to talk to them over the phone. Later, once you've finished onboarding, if you have a subscription of 10 or more users, a dedicated Account Manager is added to your #admins room for ongoing product support. + +## Can I force a chat to stay at the top of the chats list? +You sure can! Click on the chat you want to keep at the top of the list, and then click the small **pin** icon. If you want to unpin a chat, just click the **pin** icon again. + +# Deep Dive +## Chat display, aka Priority Mode +The way your chats display in the left-hand menu is customizable. We offer two different options; Most Recent mode and _#focus_ mode. + +- Most Recent mode will display all chats by default, sort them by the most recent, and keep your pinned chats at the top of the list. +- #focus mode will display only unread and pinned chats, and will sort them alphabetically. This setting is perfect for when you need to cut distractions and focus on a crucial project. + +You can find your display mode by clicking on your User Icon > Preferences > Priority Mode. + +## Inviting someone to Expensify Chat +If the person you want to chat with doesn’t appear in your contact list, simply type their email or phone number to invite them to chat! From there, they will receive an email with instructions and a link to create an account. + +Once they click the link, a new.expensify.com account is set up for them automatically (if they don't have one already), and they can start chatting with you immediately! + +## Flagging content as offensive +In order to maintain a safe community for our users, Expensify provides tools to report offensive content and unwanted behavior in Expensify Chat. If you see a message (or attachment/image) from another user that you’d like our moderators to review, you can flag it by clicking the flag icon in the message context menu (on desktop) or holding down on the message and selecting “Flag as offensive” (on mobile). + +![Moderation Context Menu](https://help.expensify.com/assets/images/moderation-context-menu.png){:width="100%"} + +Once the flag is selected, you will be asked to categorize the message (such as spam, bullying, and harassment). Select what you feel best represents the issue is with the content, and you’re done - the message will be sent off to our internal team for review. + +![Moderation Flagging Options](https://help.expensify.com/assets/images/moderation-flag-page.png){:width="100%"} + +Depending on the severity of the offense, messages can be hidden (with an option to reveal) or fully removed, and in extreme cases, the sender of the message can be temporarily or permanently blocked from posting. + +You will receive a whisper from Concierge any time your content has been flagged, as well as when you have successfully flagged a piece of content. + +![Moderation Reportee Whisper](https://help.expensify.com/assets/images/moderation-reportee-whisper.png){:width="100%"} +![Moderation Reporter Whisper](https://help.expensify.com/assets/images/moderation-reporter-whisper.png){:width="100%"} + +*Note: Any message sent in public chat rooms are automatically reviewed by an automated system looking for offensive content and sent to our moderators for final decisions if it is found.* diff --git a/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Admins.md b/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Admins.md new file mode 100644 index 000000000000..31de150d5b5e --- /dev/null +++ b/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Admins.md @@ -0,0 +1,27 @@ +--- +title: Expensify Chat for Admins +description: Best Practices for Admins settings up Expensify Chat +--- + +## Overview +Expensify Chat is an incredible way to build a community and foster long-term relationships between event producers and attendees, or attendees with each other. Admins are a huge factor in the success of the connections built in Expensify Chat during the events, as they are generally the drivers of the conference schedule, and help ensure safety and respect is upheld by all attendees both on and offline. + +## Getting Started +We’ve rounded up some resources to get you set up on Expensify Chat and ready to start connecting with your session attendees: +- [How to get set up and start using Expensify Chat](https://help.expensify.com/articles/other/Everything-About-Chat#how-to-use-chat-in-expensify) +- [How to format text in Expensify Chat](https://help.expensify.com/articles/other/Everything-About-Chat#how-to-format-text) +- [How to flag content and/or users for moderation](https://help.expensify.com/articles/other/Everything-About-Chat#flagging-content-as-offensive) + +## Admin Best Practices +In order to get the most out of Expensify Chat, we created a list of best practices for admins to review in order to use the tool to its fullest capabilities. + +**During the conference:** +- At a minimum, send 3 announcements throughout the day to create awareness of any sessions, activations, contests, or parties you want to promote. +- Communicate with the Expensify Team in the #admins room if you see anything you have questions about or are unsure of to make sure we’re resolving issues together ASAP. +- As an admin, It’s up to you to help keep your conference community safe and respectful. [Flag any content for moderation](https://help.expensify.com/articles/other/Everything-About-Chat#flagging-content-as-offensive) that does not fit your culture and values to keep chatrooms a positive experience for everyone involved. + +**After the conference:** +- The rooms will all stay open after the conference ends, so encourage speakers to keep engaging as long as the conversation is going in their session room. +- Continue sharing photos and videos from the event or anything fun in #social as part of a wrap up for everyone. +- Use the #announce room to give attendees a sneak preview of your next event. +- \ No newline at end of file diff --git a/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Conference-Attendees.md b/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Conference-Attendees.md new file mode 100644 index 000000000000..3d30237dca5a --- /dev/null +++ b/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Conference-Attendees.md @@ -0,0 +1,35 @@ +--- +title: Expensify Chat for Conference Attendees +description: Best Practices for Conference Attendees +--- + +## Overview +Expensify Chat is the best way to meet and network with other event attendees. No more hunting down your contacts by walking the floor or trying to find someone in crowds at a party. Instead, you can use Expensify Chat to network and collaborate with others throughout the conference. + +To help get you set up for a great event, we’ve created a guide to help you get the most out of using Expensify Chat at the event you’re attending. + +## Getting Started +We’ve rounded up some resources to get you set up on Expensify Chat and ready to start connecting with your fellow attendees: + +- [How to get set up and start using Expensify Chat](https://help.expensify.com/articles/other/Everything-About-Chat#how-to-use-chat-in-expensify) +- [How to format text in Expensify Chat](https://help.expensify.com/articles/other/Everything-About-Chat#how-to-format-text) +- [How to flag content and/or users for moderation](https://help.expensify.com/articles/other/Everything-About-Chat#flagging-content-as-offensive) + +## Chat Best Practices +To get the most out of your experience at your conference and engage people in a meaningful conversation that will fulfill your goals instead of turning people off, here are some tips on what to do and not to do as an event attendee using Expensify Chat: + +**Do:** +- Chat about non-business topics like where the best coffee is around the event, what great lunch options are available, or where the parties are happening that night! +- Share pictures of your travel before the event to hype everyone up, during the event if you met that person you’ve been meaning to see for years, or a fun pic from a party. +- Try to create fun groups with your fellow attendees around common interests like touring a local sight, going for a morning run, or trying a famous restaurant. + +**Don't:** +- Pitch your services in public rooms like #social or speaking session rooms. +- Start a first message with a stranger with a sales pitch. +- Discuss controversial topics such as politics, religion, or anything you wouldn’t say on a first date. +- In general just remember that you are still here for business, your profile is public, and you’re representing yourself & company, so do not say anything you wouldn’t feel comfortable sharing in a business setting. + +**Pro-Tips:** +Get active in Chat early and often by having real conversations around thought leadership or non-business discussions to stand out from the crowd! Also if you’re in a session and are afraid to ask a question, just ask in the chat room to make sure you can discuss it with the speaker after the session ends. + +By following these tips you’ll ensure that your messages will not be [flagged for moderation](https://help.expensify.com/articles/other/Everything-About-Chat#flagging-content-as-offensive) and you will not mess it up for the rest of us. diff --git a/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Conference-Speakers.md b/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Conference-Speakers.md new file mode 100644 index 000000000000..5bd52425d92b --- /dev/null +++ b/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Conference-Speakers.md @@ -0,0 +1,39 @@ +--- +title: Expensify Chat for Conference Speakers +description: Best Practices for Conference Speakers +--- + +## Overview +Are you a speaker at an event? Great! We're delighted to provide you with an extraordinary opportunity to connect with your session attendees using Expensify Chat — before, during, and after the event. Expensify Chat offers a powerful platform for introducing yourself and your topic, fostering engaging discussions about your presentation, and maintaining the conversation with attendees even after your session is over. + +## Getting Started +We’ve rounded up some resources to get you set up on Expensify Chat and ready to start connecting with your session attendees: + +- [How to get set up and start using Expensify Chat](https://help.expensify.com/articles/other/Everything-About-Chat#how-to-use-chat-in-expensify) +- [How to format text in Expensify Chat](https://help.expensify.com/articles/other/Everything-About-Chat#how-to-format-text) +- [How to flag content and/or users for moderation](https://help.expensify.com/articles/other/Everything-About-Chat#flagging-content-as-offensive) + +## Setting Up a Chatroom for Your Session: Checklist +To make the most of Expensify Chat for your session, here's a handy checklist: +- Confirm that your session has an Expensify Chat room, and have the URL link ready to share with attendees in advance. + - You can find the link by clicking on the avatar for your chatroom > “Share Code” > “Copy URL to dashboard” +- Join the chat room as soon as it's ready to begin engaging with your audience right from the start. +- Consider having a session moderator with you on the day to assist with questions and discussions while you're presenting. +- Include the QR code for your session's chat room in your presentation slides. Displaying it prominently on every slide ensures that attendees can easily join the chat throughout your presentation. + +## Tips to Enhance Engagement Around Your Session +By following these steps and utilizing Expensify Chat, you can elevate your session to promote valuable interactions with your audience, and leave a lasting impact beyond the conference. We can't wait to see your sessions thrive with the power of Expensify Chat! + +**Before the event:** +- Share your session's QR code or URL on your social media platforms, your website or other platforms to encourage attendees to join the conversation early on. +- Encourage attendees to ask questions in the chat room before the event, enabling you to tailor your session and address their specific interests. + +**During the event:** +- Keep your QR code readily available during the conference by saving it as a photo on your phone or setting it as your locked screen image. This way, you can easily share it with others you meet. +- Guide your audience back to the QR code and encourage them to ask questions, fostering interactive discussions. + +**After the event:** +- Continue engaging with attendees by responding to their questions and comments, helping you expand your audience and sustain interest. +- Share your presentation slides after the event as well as any photos from your session, allowing attendees to review and share your content with their networks if they want to. + +If you have any questions on how Expensify Chat works, head to our guide [here](https://help.expensify.com/articles/other/Everything-About-Chat). diff --git a/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-Playbook-For-Conferences.md b/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-Playbook-For-Conferences.md new file mode 100644 index 000000000000..8f806bb03146 --- /dev/null +++ b/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-Playbook-For-Conferences.md @@ -0,0 +1,93 @@ +--- +title: Expensify Chat Playbook for Conferences +description: Best practices for how to deploy Expensify Chat for your conference +--- +## Overview +To help make setting up Expensify Chat for your event and your attendees super simple, we’ve created a guide for all of the technical setup details. + +## Who you are +As a conference organizer, you’re expected to amaze and inspire attendees. You want attendees to get to the right place on time, engage with the speakers, and create relationships with each other that last long after the conference is done. Enter Expensify Chat, a free feature that allows attendees to interact with organizers and other attendees in realtime. With Expensify Chat, you can: + +- Communicate logistics and key information +- Foster conference wide attendee networking +- Organize conversations by topic and audience +- Continue conversations long after the event itself +- Digitize attendee social interaction +- Create an inclusive environment for virtual attendees + +Sounds good? Great! In order to ensure your team, your speakers, and your attendees have the best experience possible, we’ve created a guide on how to use Expensify Chat at your event. + +*Let’s get started!* + + +## Support +Connect with your dedicated account manager in any new.expensify.com #admins room. Your account manager is excited to brainstorm the best ways to make the most out of your event and work through any questions you have about the setup steps below. + +We also have a number of [moderation tools](https://help.expensify.com/articles/other/Everything-About-Chat#flagging-content-as-offensive) available to admins to help make sure your event is seamless, safe, and fun! + +## Step by step instructions for setting up your conference on Expensify Chat +Based on our experience running conferences atop Expensify Chat, we recommend the following simple steps: + +### Step 1: Create your event workspace in Expensify +To create your event workspace in Expensify: +1. In [new.expensify.com](https://new.expensify.com): “+” > “New workspace” +1. Name the workspace (e.g. “ExpensiCon”) + +### Step 2: Set up all the Expensify Chat rooms you want to feature at your event +**Protip**: Your Expensify account manager can complete this step with you. Chat them in #admins on new.expensify.com to coordinate! + +To create a new chat room: +1. Go to [new.expensify.com](https://new.expensify.com) +1. Go to “+” > New room +1. Name the room (e.g. “#social”) +1. Select the workspace created in step 1 +1. Select “Public” visibility +1. Repeat for each room + +For an easy-to-follow event, we recommend creating these chat rooms: + +- **#social** - This room will include all attendees, speakers, and members of your organizing team. You can use this room to discuss social events, happy hours, dinners, or encourage attendees to mingle, share photos and connect. +- **#announcements** - This room will be used as your main announcement channel, and should only be used by organizers to announce schedule updates or anything important that your attendees need to know. Everyone in your policy will be invited to this channel, but chatting in here isn’t encouraged so to keep the noise to a minimum. +- **Create an individual room for each session** - Attendees will be able to engage with the speaker/session leader and can ask questions about their content either before/during/after the session. +- **Create a room with your Expensify account manager/s** - We can use this room to coordinate using Expensify Chat before, during, and after the event. + +**Protip** Check out our [moderation tools](https://help.expensify.com/articles/other/Everything-About-Chat#flagging-content-as-offensive) to help flag comments deemed to be spam, inconsiderate, intimidating, bullying, harassment, assault. On any comment just click the flag icon to moderate conversation. + +### Step 3: Add chat room QR codes to the applicable session slide deck +Gather QR codes: +1. Go to [new.expensify.com](https://new.expensify.com) +1. Click into a room and click the room name or avatar in the top header +1. Go into Share Code +1. Screenshot the QR code to add to your deck + +Add the QR code to every slide so that if folks forget to scan the QR code at the beginning of the presentation, they can still join the discussion. + +### Step 4: Plan out your messaging and cadence before the event begins +Expensify Chat is a great place to provide updates leading up to your event -- share news, get folks excited about speakers, and let attendees know of crucial event information like recommended attire, travel info, and more. For example, you might consider: + +**Prep your announcements:** +- Create a document containing drafts of the key messages you intend to send throughout the day. +- If your event's agenda is broken up into hourly blocks, create a separate section for each hour of the event, to make it easy to find the correct section at the right time. +- Start each day with a review of the daily agenda, such as a bullet list summarizing what's happening hour by hour. + +**Post your updates:** +- Designate a team member to post each update in #announce at the designated time. +- Each hour, send a message listing exactly what is happening next – if there are multiple sessions happening simultaneously, list out each, along with a description of the session, a reminder of where it's located, and (most importantly) a link to the chat room for that session +- Write the messages in [markdown format](https://help.expensify.com/articles/other/Everything-About-Chat#how-to-format-text), such that they can be copy/pasted directly into Expensify Chat for sending. + - If there is some formatting issue upon posting, no problem: just edit the comment after sending, and it'll be fixed for everyone. +- We’d also recommend posting your updates on new lines so that if someone has a question about a certain item they can ask in a thread pertaining to that topic, rather than in one consolidated block. + +**Protip**: Your account manager can help you create this document, and would be happy to send each message at the appointed time for you. + +### Step 5: Share Expensify Chat How-To Resources with Speakers, Attendees, Admins +We’ve created a few helpful best practice docs for your speakers, admins, and attendees to help navigate using Expensify Chat at your event. Feel free to share the links below with them! + +- [Expensify Chat for Conference Attendees](https://help.expensify.com/articles/other/Expensify-Chat-For-Conference-Attendees) +- [Expensify Chat for Conference Speakers](https://help.expensify.com/articles/other/Expensify-Chat-For-Conference-Speakers) +- [Expensify Chat for Admins](https://help.expensify.com/articles/other/Expensify-Chat-For-Admins) + +### Step 6: Follow up with attendees after the event +Continue the connections by using Expensify Chat to keep your conference community connected. Encourage attendees to share photos, their favorite memories, funny stories, and more. + +## Conclusion +Once you have completed the above steps you are ready to host your conference on Expensify Chat! Let your account manager know any questions you have over in your [new.expensify.com](https://new.expensify.com) #admins room and start driving activity in your Expensify Chat rooms. Once you’ve reviewed this doc you should have the foundations in place, so a great next step is to start training your speakers on how to use Expensify Chat for their sessions. Coordinate with your account manager to make sure everything goes smoothly! diff --git a/docs/articles/new-expensify/integrations/accounting-integrations/QuickBooks-Online.md b/docs/articles/new-expensify/integrations/accounting-integrations/QuickBooks-Online.md new file mode 100644 index 000000000000..ed4d127d5c26 --- /dev/null +++ b/docs/articles/new-expensify/integrations/accounting-integrations/QuickBooks-Online.md @@ -0,0 +1,5 @@ +--- +title: QuickBooks Online +description: QuickBooks Online +--- +## Resources Coming Soon! From 09266bea52f1f45abff4e844083972b768621525 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 20 Sep 2023 20:27:36 +0530 Subject: [PATCH 100/151] add coming soon articles for new expensify --- docs/articles/new-expensify/account-settings/Coming-Soon.md | 4 ++++ .../bank-accounts-and-credit-cards/Coming-Soon.md | 4 ++++ .../new-expensify/expense-and-report-features/Coming-Soon.md | 4 ++++ docs/articles/new-expensify/expensify-card/Coming-Soon.md | 4 ++++ docs/articles/new-expensify/exports/Coming-Soon.md | 4 ++++ .../manage-employees-and-report-approvals/Coming-Soon.md | 4 ++++ docs/articles/new-expensify/send-payments/Coming-Soon.md | 4 ++++ .../workspace-and-domain-settings/Coming-Soon.md | 4 ++++ 8 files changed, 32 insertions(+) create mode 100644 docs/articles/new-expensify/account-settings/Coming-Soon.md create mode 100644 docs/articles/new-expensify/bank-accounts-and-credit-cards/Coming-Soon.md create mode 100644 docs/articles/new-expensify/expense-and-report-features/Coming-Soon.md create mode 100644 docs/articles/new-expensify/expensify-card/Coming-Soon.md create mode 100644 docs/articles/new-expensify/exports/Coming-Soon.md create mode 100644 docs/articles/new-expensify/manage-employees-and-report-approvals/Coming-Soon.md create mode 100644 docs/articles/new-expensify/send-payments/Coming-Soon.md create mode 100644 docs/articles/new-expensify/workspace-and-domain-settings/Coming-Soon.md diff --git a/docs/articles/new-expensify/account-settings/Coming-Soon.md b/docs/articles/new-expensify/account-settings/Coming-Soon.md new file mode 100644 index 000000000000..6b85bb0364b5 --- /dev/null +++ b/docs/articles/new-expensify/account-settings/Coming-Soon.md @@ -0,0 +1,4 @@ +--- +title: Coming Soon +description: Coming Soon +--- diff --git a/docs/articles/new-expensify/bank-accounts-and-credit-cards/Coming-Soon.md b/docs/articles/new-expensify/bank-accounts-and-credit-cards/Coming-Soon.md new file mode 100644 index 000000000000..6b85bb0364b5 --- /dev/null +++ b/docs/articles/new-expensify/bank-accounts-and-credit-cards/Coming-Soon.md @@ -0,0 +1,4 @@ +--- +title: Coming Soon +description: Coming Soon +--- diff --git a/docs/articles/new-expensify/expense-and-report-features/Coming-Soon.md b/docs/articles/new-expensify/expense-and-report-features/Coming-Soon.md new file mode 100644 index 000000000000..6b85bb0364b5 --- /dev/null +++ b/docs/articles/new-expensify/expense-and-report-features/Coming-Soon.md @@ -0,0 +1,4 @@ +--- +title: Coming Soon +description: Coming Soon +--- diff --git a/docs/articles/new-expensify/expensify-card/Coming-Soon.md b/docs/articles/new-expensify/expensify-card/Coming-Soon.md new file mode 100644 index 000000000000..6b85bb0364b5 --- /dev/null +++ b/docs/articles/new-expensify/expensify-card/Coming-Soon.md @@ -0,0 +1,4 @@ +--- +title: Coming Soon +description: Coming Soon +--- diff --git a/docs/articles/new-expensify/exports/Coming-Soon.md b/docs/articles/new-expensify/exports/Coming-Soon.md new file mode 100644 index 000000000000..6b85bb0364b5 --- /dev/null +++ b/docs/articles/new-expensify/exports/Coming-Soon.md @@ -0,0 +1,4 @@ +--- +title: Coming Soon +description: Coming Soon +--- diff --git a/docs/articles/new-expensify/manage-employees-and-report-approvals/Coming-Soon.md b/docs/articles/new-expensify/manage-employees-and-report-approvals/Coming-Soon.md new file mode 100644 index 000000000000..6b85bb0364b5 --- /dev/null +++ b/docs/articles/new-expensify/manage-employees-and-report-approvals/Coming-Soon.md @@ -0,0 +1,4 @@ +--- +title: Coming Soon +description: Coming Soon +--- diff --git a/docs/articles/new-expensify/send-payments/Coming-Soon.md b/docs/articles/new-expensify/send-payments/Coming-Soon.md new file mode 100644 index 000000000000..6b85bb0364b5 --- /dev/null +++ b/docs/articles/new-expensify/send-payments/Coming-Soon.md @@ -0,0 +1,4 @@ +--- +title: Coming Soon +description: Coming Soon +--- diff --git a/docs/articles/new-expensify/workspace-and-domain-settings/Coming-Soon.md b/docs/articles/new-expensify/workspace-and-domain-settings/Coming-Soon.md new file mode 100644 index 000000000000..6b85bb0364b5 --- /dev/null +++ b/docs/articles/new-expensify/workspace-and-domain-settings/Coming-Soon.md @@ -0,0 +1,4 @@ +--- +title: Coming Soon +description: Coming Soon +--- From 247055e11ca33db21cee76119a86249df1614c18 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 20 Sep 2023 20:56:34 +0530 Subject: [PATCH 101/151] fix lint --- .github/scripts/createDocsRoutes.js | 52 ++++++++++++++--------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/.github/scripts/createDocsRoutes.js b/.github/scripts/createDocsRoutes.js index d74b00172fcf..a9a3e1462225 100644 --- a/.github/scripts/createDocsRoutes.js +++ b/.github/scripts/createDocsRoutes.js @@ -48,32 +48,6 @@ function pushOrCreateEntry(hubs, hub, key, entry) { } } -function run() { - const expensifyClassicArticleHubs = fs.readdirSync(`${docsDir}/articles/${platformNames.expensifyClassic}`); - const newExpensifyArticleHubs = fs.readdirSync(`${docsDir}/articles/${platformNames.newExpensify}`); - - const expensifyClassicRoute = routes.platforms.find((platform) => platform.href === platformNames.expensifyClassic); - const newExpensifyRoute = routes.platforms.find((platform) => platform.href === platformNames.newExpensify); - - if (expensifyClassicArticleHubs.length !== expensifyClassicRoute.hubs.length) { - console.error(warn(platformNames.expensifyClassic)); - return 1; - } - - if (newExpensifyArticleHubs.length !== newExpensifyRoute.hubs.length) { - console.error(warn(platformNames.newExpensify)); - return 1; - } - - createHubsWithArticles(expensifyClassicArticleHubs, platformNames.expensifyClassic, expensifyClassicRoute.hubs); - createHubsWithArticles(newExpensifyArticleHubs, platformNames.newExpensify, newExpensifyRoute.hubs); - - // Convert the object to YAML and write it to the file - let yamlString = yaml.dump(routes); - yamlString = disclaimer + yamlString; - fs.writeFileSync(`${docsDir}/_data/routes.yml`, yamlString); -} - /** * Add articles and sections to hubs * @param {Array} hubs - The hubs inside docs/articles/ for a platform @@ -109,6 +83,32 @@ function createHubsWithArticles(hubs, platformName, routeHubs) { }); } +function run() { + const expensifyClassicArticleHubs = fs.readdirSync(`${docsDir}/articles/${platformNames.expensifyClassic}`); + const newExpensifyArticleHubs = fs.readdirSync(`${docsDir}/articles/${platformNames.newExpensify}`); + + const expensifyClassicRoute = routes.platforms.find((platform) => platform.href === platformNames.expensifyClassic); + const newExpensifyRoute = routes.platforms.find((platform) => platform.href === platformNames.newExpensify); + + if (expensifyClassicArticleHubs.length !== expensifyClassicRoute.hubs.length) { + console.error(warn(platformNames.expensifyClassic)); + return 1; + } + + if (newExpensifyArticleHubs.length !== newExpensifyRoute.hubs.length) { + console.error(warn(platformNames.newExpensify)); + return 1; + } + + createHubsWithArticles(expensifyClassicArticleHubs, platformNames.expensifyClassic, expensifyClassicRoute.hubs); + createHubsWithArticles(newExpensifyArticleHubs, platformNames.newExpensify, newExpensifyRoute.hubs); + + // Convert the object to YAML and write it to the file + let yamlString = yaml.dump(routes); + yamlString = disclaimer + yamlString; + fs.writeFileSync(`${docsDir}/_data/routes.yml`, yamlString); +} + try { run(); } catch (error) { From 7cf684df33c6860f53dcf8585b9bf422e5c5d7b8 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 20 Sep 2023 21:06:45 +0530 Subject: [PATCH 102/151] fix lint --- .github/scripts/createDocsRoutes.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/scripts/createDocsRoutes.js b/.github/scripts/createDocsRoutes.js index a9a3e1462225..f12615ba2184 100644 --- a/.github/scripts/createDocsRoutes.js +++ b/.github/scripts/createDocsRoutes.js @@ -87,8 +87,8 @@ function run() { const expensifyClassicArticleHubs = fs.readdirSync(`${docsDir}/articles/${platformNames.expensifyClassic}`); const newExpensifyArticleHubs = fs.readdirSync(`${docsDir}/articles/${platformNames.newExpensify}`); - const expensifyClassicRoute = routes.platforms.find((platform) => platform.href === platformNames.expensifyClassic); - const newExpensifyRoute = routes.platforms.find((platform) => platform.href === platformNames.newExpensify); + const expensifyClassicRoute = _.find(routes.platforms, (platform) => platform.href === platformNames.expensifyClassic); + const newExpensifyRoute = _.find(routes.platforms, (platform) => platform.href === platformNames.newExpensify); if (expensifyClassicArticleHubs.length !== expensifyClassicRoute.hubs.length) { console.error(warn(platformNames.expensifyClassic)); From 4909cb20567c69bdcbf3307f6fb7fd6af23ac5a9 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 20 Sep 2023 21:07:47 +0530 Subject: [PATCH 103/151] rename --- .github/scripts/createDocsRoutes.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/scripts/createDocsRoutes.js b/.github/scripts/createDocsRoutes.js index f12615ba2184..8f984826ad93 100644 --- a/.github/scripts/createDocsRoutes.js +++ b/.github/scripts/createDocsRoutes.js @@ -2,7 +2,7 @@ const yaml = require('js-yaml'); const fs = require('fs'); const _ = require('underscore'); -const warn = (platform) => `Number of hubs in _routes.yml does not match number of hubs in docs/${platform}/articles. Please update _routes.yml with hub info.`; +const warnMessage = (platform) => `Number of hubs in _routes.yml does not match number of hubs in docs/${platform}/articles. Please update _routes.yml with hub info.`; const disclaimer = '# This file is auto-generated. Do not edit it directly. Use npm run createDocsRoutes instead.\n'; const docsDir = `${process.cwd()}/docs`; const routes = yaml.load(fs.readFileSync(`${docsDir}/_data/_routes.yml`, 'utf8')); @@ -91,12 +91,12 @@ function run() { const newExpensifyRoute = _.find(routes.platforms, (platform) => platform.href === platformNames.newExpensify); if (expensifyClassicArticleHubs.length !== expensifyClassicRoute.hubs.length) { - console.error(warn(platformNames.expensifyClassic)); + console.error(warnMessage(platformNames.expensifyClassic)); return 1; } if (newExpensifyArticleHubs.length !== newExpensifyRoute.hubs.length) { - console.error(warn(platformNames.newExpensify)); + console.error(warnMessage(platformNames.newExpensify)); return 1; } From 8b438b5931843c9bf31406aac1876954e215fc65 Mon Sep 17 00:00:00 2001 From: Mahesh Date: Wed, 20 Sep 2023 22:38:55 +0530 Subject: [PATCH 104/151] fix transition of profiles on request confirmation page --- src/pages/ProfilePage.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/pages/ProfilePage.js b/src/pages/ProfilePage.js index b515da04b7be..47eb44de6f91 100755 --- a/src/pages/ProfilePage.js +++ b/src/pages/ProfilePage.js @@ -99,13 +99,6 @@ const getPhoneNumber = (details) => { function ProfilePage(props) { const accountID = Number(lodashGet(props.route.params, 'accountID', 0)); - // eslint-disable-next-line rulesdir/prefer-early-return - useEffect(() => { - if (ValidationUtils.isValidAccountRoute(accountID)) { - PersonalDetails.openPublicProfilePage(accountID); - } - }, [accountID]); - const details = lodashGet(props.personalDetails, accountID, ValidationUtils.isValidAccountRoute(accountID) ? {} : {isloading: false}); const displayName = details.displayName ? details.displayName : props.translate('common.hidden'); @@ -143,6 +136,12 @@ function ProfilePage(props) { const chatReportWithCurrentUser = !isCurrentUser && !Session.isAnonymousUser() ? ReportUtils.getChatByParticipants([accountID]) : 0; + useEffect(() => { + if (ValidationUtils.isValidAccountRoute(accountID) && !hasMinimumDetails) { + PersonalDetails.openPublicProfilePage(accountID); + } + }, [accountID, hasMinimumDetails]); + return ( Date: Wed, 20 Sep 2023 23:32:19 +0530 Subject: [PATCH 105/151] fix lint error --- src/pages/ProfilePage.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/ProfilePage.js b/src/pages/ProfilePage.js index 47eb44de6f91..36fafa94d097 100755 --- a/src/pages/ProfilePage.js +++ b/src/pages/ProfilePage.js @@ -136,6 +136,7 @@ function ProfilePage(props) { const chatReportWithCurrentUser = !isCurrentUser && !Session.isAnonymousUser() ? ReportUtils.getChatByParticipants([accountID]) : 0; + // eslint-disable-next-line rulesdir/prefer-early-return useEffect(() => { if (ValidationUtils.isValidAccountRoute(accountID) && !hasMinimumDetails) { PersonalDetails.openPublicProfilePage(accountID); From 9adef6e47a56810e721b930566928dd08532710c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Miko=C5=82ajczak?= Date: Wed, 20 Sep 2023 20:19:27 +0200 Subject: [PATCH 106/151] add ForwardRef note to STYLE.md --- contributingGuides/STYLE.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/contributingGuides/STYLE.md b/contributingGuides/STYLE.md index b615104f6aab..0a88ecd7bda8 100644 --- a/contributingGuides/STYLE.md +++ b/contributingGuides/STYLE.md @@ -491,6 +491,19 @@ When writing a function component you must ALWAYS add a `displayName` property a export default Avatar; ``` +## Forwarding refs + +When forwarding a ref define named component and pass it directly to the `forwardRef`. By doing this we remove potential extra layer in React tree in form of anonymous component. + +```javascript + function FancyInput(props, ref) { + ... + return + } + + export default React.forwardRef(FancyInput) +``` + ## Stateless components vs Pure Components vs Class based components vs Render Props - When to use what? Class components are DEPRECATED. Use function components and React hooks. From 93d58d5a95ae621c85f51e6aa41756973d513da9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Miko=C5=82ajczak?= Date: Wed, 20 Sep 2023 20:23:52 +0200 Subject: [PATCH 107/151] add changes to ForwardRef section in TS_CHEATSHEET --- contributingGuides/TS_CHEATSHEET.md | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/contributingGuides/TS_CHEATSHEET.md b/contributingGuides/TS_CHEATSHEET.md index df6d70b5ae90..1e330dafb7cf 100644 --- a/contributingGuides/TS_CHEATSHEET.md +++ b/contributingGuides/TS_CHEATSHEET.md @@ -43,7 +43,9 @@ - [1.2](#forwardRef) **`forwardRef`** ```ts - import { forwardRef, useRef, ReactNode } from "react"; + // CustomTextInput.tsx + + import { forwardRef, useRef, ReactNode, ForwardedRef } from "react"; import { TextInput, View } from "react-native"; export type CustomTextInputProps = { @@ -51,16 +53,18 @@ children?: ReactNode; }; - const CustomTextInput = forwardRef( - (props, ref) => { - return ( - - - {props.children} - - ); - } - ); + function CustomTextInput(props: CustomTextInputProps, ref: ForwardedRef) { + return ( + + + {props.children} + + ); + }; + + export default forwardRef(CustomTextInput); + + // ParentComponent.tsx function ParentComponent() { const ref = useRef(); From ceee888aac987759cb98395bcae4aaccc942c2ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Miko=C5=82ajczak?= Date: Wed, 20 Sep 2023 20:27:31 +0200 Subject: [PATCH 108/151] add generic component annotation to forwardRef section in TS_CHEATSHEET --- contributingGuides/TS_CHEATSHEET.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contributingGuides/TS_CHEATSHEET.md b/contributingGuides/TS_CHEATSHEET.md index 1e330dafb7cf..199127acda97 100644 --- a/contributingGuides/TS_CHEATSHEET.md +++ b/contributingGuides/TS_CHEATSHEET.md @@ -48,12 +48,12 @@ import { forwardRef, useRef, ReactNode, ForwardedRef } from "react"; import { TextInput, View } from "react-native"; - export type CustomTextInputProps = { - label: string; + export type CustomTextInputProps = { + label: T; children?: ReactNode; }; - function CustomTextInput(props: CustomTextInputProps, ref: ForwardedRef) { + function CustomTextInput(props: CustomTextInputProps, ref: ForwardedRef) { return ( From aed5aa94232abe7e049b389fe5eb793ec5356456 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Miko=C5=82ajczak?= Date: Wed, 20 Sep 2023 20:30:41 +0200 Subject: [PATCH 109/151] Revert "add generic component annotation to forwardRef section in TS_CHEATSHEET" This reverts commit ceee888aac987759cb98395bcae4aaccc942c2ff. --- contributingGuides/TS_CHEATSHEET.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contributingGuides/TS_CHEATSHEET.md b/contributingGuides/TS_CHEATSHEET.md index 199127acda97..1e330dafb7cf 100644 --- a/contributingGuides/TS_CHEATSHEET.md +++ b/contributingGuides/TS_CHEATSHEET.md @@ -48,12 +48,12 @@ import { forwardRef, useRef, ReactNode, ForwardedRef } from "react"; import { TextInput, View } from "react-native"; - export type CustomTextInputProps = { - label: T; + export type CustomTextInputProps = { + label: string; children?: ReactNode; }; - function CustomTextInput(props: CustomTextInputProps, ref: ForwardedRef) { + function CustomTextInput(props: CustomTextInputProps, ref: ForwardedRef) { return ( From de9f1e5cae388819947af025d1079bc3e3efe122 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Wed, 20 Sep 2023 23:44:01 +0200 Subject: [PATCH 110/151] Improve handling for window.matches --- src/libs/DeviceCapabilities/canUseTouchScreen/index.ts | 2 +- src/libs/DeviceCapabilities/hasHoverSupport/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/DeviceCapabilities/canUseTouchScreen/index.ts b/src/libs/DeviceCapabilities/canUseTouchScreen/index.ts index 72798159e36c..9e21f5a42b5d 100644 --- a/src/libs/DeviceCapabilities/canUseTouchScreen/index.ts +++ b/src/libs/DeviceCapabilities/canUseTouchScreen/index.ts @@ -21,7 +21,7 @@ const canUseTouchScreen: CanUseTouchScreen = () => { hasTouchScreen = (navigator as ExtendedNavigator).msMaxTouchPoints > 0; } else { // Same case as for Navigator - TypeScript thinks that matchMedia is obligatory property of window although it may not be - const mQ = (window as Partial).matchMedia && matchMedia('(pointer:coarse)'); + const mQ = window.matchMedia?.('(pointer:coarse)'); if (mQ && mQ.media === '(pointer:coarse)') { hasTouchScreen = !!mQ.matches; } else if ('orientation' in window) { diff --git a/src/libs/DeviceCapabilities/hasHoverSupport/index.ts b/src/libs/DeviceCapabilities/hasHoverSupport/index.ts index df62e6681548..1ff0f461db69 100644 --- a/src/libs/DeviceCapabilities/hasHoverSupport/index.ts +++ b/src/libs/DeviceCapabilities/hasHoverSupport/index.ts @@ -3,6 +3,6 @@ import HasHoverSupport from './types'; /** * Allows us to identify whether the platform is hoverable. */ -const hasHoverSupport: HasHoverSupport = () => window.matchMedia('(hover: hover) and (pointer: fine)').matches; +const hasHoverSupport: HasHoverSupport = () => window.matchMedia?.('(hover: hover) and (pointer: fine)').matches; export default hasHoverSupport; From 00df2fcca20a0824f8ad9578ef99312fa43631f6 Mon Sep 17 00:00:00 2001 From: jeet-dhandha Date: Thu, 21 Sep 2023 04:39:54 +0530 Subject: [PATCH 111/151] fix: cursor issue --- src/components/MenuItemRenderHTMLTitle/index.js | 2 +- src/styles/styles.js | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/components/MenuItemRenderHTMLTitle/index.js b/src/components/MenuItemRenderHTMLTitle/index.js index e4f0b27c4f09..f91e70dc1249 100644 --- a/src/components/MenuItemRenderHTMLTitle/index.js +++ b/src/components/MenuItemRenderHTMLTitle/index.js @@ -10,7 +10,7 @@ const defaultProps = {}; function MenuItemRenderHTMLTitle(props) { return ( - + ); diff --git a/src/styles/styles.js b/src/styles/styles.js index 2e411e7e63df..b218a2eb435b 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -1661,6 +1661,16 @@ const styles = (theme) => ({ ...wordBreak.breakWord, }, + renderHTMLTitle: { + color: theme.text, + fontSize: variables.fontSizeNormal, + fontFamily: fontFamily.EXP_NEUE, + lineHeight: variables.lineHeightXLarge, + maxWidth: '100%', + ...whiteSpace.preWrap, + ...wordBreak.breakWord, + }, + chatItemComposeWithFirstRow: { minHeight: 90, }, From 1e1b83ce51f2b75d768cf78a234d05d04d373927 Mon Sep 17 00:00:00 2001 From: jeet-dhandha Date: Thu, 21 Sep 2023 04:41:43 +0530 Subject: [PATCH 112/151] Removed extra scrollview and style --- src/components/MenuItem.js | 9 ++------- src/styles/styles.js | 4 ---- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/components/MenuItem.js b/src/components/MenuItem.js index 12dbbf1e4903..268351699567 100644 --- a/src/components/MenuItem.js +++ b/src/components/MenuItem.js @@ -1,6 +1,6 @@ import _ from 'underscore'; import React, {useEffect, useMemo} from 'react'; -import {ScrollView, View} from 'react-native'; +import {View} from 'react-native'; import ExpensiMark from 'expensify-common/lib/ExpensiMark'; import Text from './Text'; import styles from '../styles/styles'; @@ -25,7 +25,6 @@ import * as Session from '../libs/actions/Session'; import Hoverable from './Hoverable'; import useWindowDimensions from '../hooks/useWindowDimensions'; import MenuItemRenderHTMLTitle from './MenuItemRenderHTMLTitle'; -import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback'; const propTypes = menuItemPropTypes; @@ -249,11 +248,7 @@ const MenuItem = React.forwardRef((props, ref) => { )} {Boolean(props.title) && (Boolean(props.shouldRenderAsHTML) || (Boolean(props.shouldParseTitle) && Boolean(html.length))) && ( - - - - - + )} {!props.shouldRenderAsHTML && !props.shouldParseTitle && Boolean(props.title) && ( ({ height: 30, width: '100%', }, - - menuItemHtmlRendererScrollView: { - maxHeight: 115, - }, }); // For now we need to export the styles function that takes the theme as an argument From 04c6a9733505bc735abb5088f10cd7267ee504d9 Mon Sep 17 00:00:00 2001 From: jeet-dhandha Date: Thu, 21 Sep 2023 04:48:14 +0530 Subject: [PATCH 113/151] fix scroll at new task page --- src/pages/tasks/NewTaskPage.js | 88 +++++++++++++++++----------------- 1 file changed, 45 insertions(+), 43 deletions(-) diff --git a/src/pages/tasks/NewTaskPage.js b/src/pages/tasks/NewTaskPage.js index 9fb600e40753..1696961764ca 100644 --- a/src/pages/tasks/NewTaskPage.js +++ b/src/pages/tasks/NewTaskPage.js @@ -1,5 +1,5 @@ import React, {useEffect, useMemo, useState} from 'react'; -import {View} from 'react-native'; +import {ScrollView, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import PropTypes from 'prop-types'; @@ -149,50 +149,52 @@ function NewTaskPage(props) { Navigation.goBack(ROUTES.NEW_TASK_DETAILS); }} /> - - - Navigation.navigate(ROUTES.NEW_TASK_TITLE)} - shouldShowRightIcon - /> - Navigation.navigate(ROUTES.NEW_TASK_DESCRIPTION)} - shouldShowRightIcon - shouldParseTitle - numberOfLinesTitle={2} - titleStyle={styles.flex1} - /> - Navigation.navigate(ROUTES.NEW_TASK_ASSIGNEE)} - shouldShowRightIcon - /> - Navigation.navigate(ROUTES.NEW_TASK_SHARE_DESTINATION)} - interactive={!props.task.parentReportID} - shouldShowRightIcon={!props.task.parentReportID} + + + + Navigation.navigate(ROUTES.NEW_TASK_TITLE)} + shouldShowRightIcon + /> + Navigation.navigate(ROUTES.NEW_TASK_DESCRIPTION)} + shouldShowRightIcon + shouldParseTitle + numberOfLinesTitle={2} + titleStyle={styles.flex1} + /> + Navigation.navigate(ROUTES.NEW_TASK_ASSIGNEE)} + shouldShowRightIcon + /> + Navigation.navigate(ROUTES.NEW_TASK_SHARE_DESTINATION)} + interactive={!props.task.parentReportID} + shouldShowRightIcon={!props.task.parentReportID} + /> + + onSubmit()} + enabledWhenOffline + buttonText={props.translate('newTaskPage.confirmTask')} + containerStyles={[styles.mh0, styles.mt5, styles.flex1, styles.ph5]} /> - onSubmit()} - enabledWhenOffline - buttonText={props.translate('newTaskPage.confirmTask')} - containerStyles={[styles.mh0, styles.mt5, styles.flex1, styles.ph5]} - /> - +
); From 8a6d44c35d1880afa2b08d7a8a826917cb0fd11d Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 21 Sep 2023 04:50:41 +0530 Subject: [PATCH 114/151] replace return with exit() --- .github/scripts/createDocsRoutes.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/scripts/createDocsRoutes.js b/.github/scripts/createDocsRoutes.js index 8f984826ad93..abb8e7692a05 100644 --- a/.github/scripts/createDocsRoutes.js +++ b/.github/scripts/createDocsRoutes.js @@ -92,12 +92,12 @@ function run() { if (expensifyClassicArticleHubs.length !== expensifyClassicRoute.hubs.length) { console.error(warnMessage(platformNames.expensifyClassic)); - return 1; + exit(1); } if (newExpensifyArticleHubs.length !== newExpensifyRoute.hubs.length) { console.error(warnMessage(platformNames.newExpensify)); - return 1; + exit(1); } createHubsWithArticles(expensifyClassicArticleHubs, platformNames.expensifyClassic, expensifyClassicRoute.hubs); @@ -113,5 +113,5 @@ try { run(); } catch (error) { console.error('A problem occurred while trying to read the directories.', error); - return 1; + exit(1); } From ed0267c654674722b3cb363b5f8181b14cebc83e Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 21 Sep 2023 05:01:24 +0530 Subject: [PATCH 115/151] fix lint --- .github/scripts/createDocsRoutes.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/scripts/createDocsRoutes.js b/.github/scripts/createDocsRoutes.js index abb8e7692a05..39cd98383de1 100644 --- a/.github/scripts/createDocsRoutes.js +++ b/.github/scripts/createDocsRoutes.js @@ -92,12 +92,12 @@ function run() { if (expensifyClassicArticleHubs.length !== expensifyClassicRoute.hubs.length) { console.error(warnMessage(platformNames.expensifyClassic)); - exit(1); + process.exit(1); } if (newExpensifyArticleHubs.length !== newExpensifyRoute.hubs.length) { console.error(warnMessage(platformNames.newExpensify)); - exit(1); + process.exit(1); } createHubsWithArticles(expensifyClassicArticleHubs, platformNames.expensifyClassic, expensifyClassicRoute.hubs); @@ -113,5 +113,5 @@ try { run(); } catch (error) { console.error('A problem occurred while trying to read the directories.', error); - exit(1); + process.exit(1); } From 6353a61b11f7dfe876a366e461c63ad7e3650937 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arthur=20Gon=C3=A7alves?= Date: Tue, 19 Sep 2023 10:19:09 +0100 Subject: [PATCH 116/151] Converted to ts addresing types and files extension --- .../BootSplash/{index.native.js => index.native.ts} | 3 ++- src/libs/BootSplash/{index.js => index.ts} | 13 +++++++++---- src/libs/BootSplash/types.ts | 9 +++++++++ 3 files changed, 20 insertions(+), 5 deletions(-) rename src/libs/BootSplash/{index.native.js => index.native.ts} (75%) rename src/libs/BootSplash/{index.js => index.ts} (66%) create mode 100644 src/libs/BootSplash/types.ts diff --git a/src/libs/BootSplash/index.native.js b/src/libs/BootSplash/index.native.ts similarity index 75% rename from src/libs/BootSplash/index.native.js rename to src/libs/BootSplash/index.native.ts index 942b3cadb74a..f0082afa46d5 100644 --- a/src/libs/BootSplash/index.native.js +++ b/src/libs/BootSplash/index.native.ts @@ -1,7 +1,8 @@ import {NativeModules} from 'react-native'; import Log from '../Log'; +import {BootSplashModule} from './types'; -const BootSplash = NativeModules.BootSplash; +const BootSplash: BootSplashModule = NativeModules.BootSplash; function hide() { Log.info('[BootSplash] hiding splash screen', false); diff --git a/src/libs/BootSplash/index.js b/src/libs/BootSplash/index.ts similarity index 66% rename from src/libs/BootSplash/index.js rename to src/libs/BootSplash/index.ts index ff7ab5562b1f..d614c06da090 100644 --- a/src/libs/BootSplash/index.js +++ b/src/libs/BootSplash/index.ts @@ -1,6 +1,7 @@ import Log from '../Log'; +import {VisibilityStatus} from './types'; -function resolveAfter(delay) { +function resolveAfter(delay: number) { return new Promise((resolve) => setTimeout(resolve, delay)); } @@ -9,16 +10,20 @@ function hide() { return document.fonts.ready.then(() => { const splash = document.getElementById('splash'); - if (splash) splash.style.opacity = 0; + if (splash) { + splash.style.opacity = '0'; + } return resolveAfter(250).then(() => { - if (!splash || !splash.parentNode) return; + if (!splash?.parentNode) { + return; + } splash.parentNode.removeChild(splash); }); }); } -function getVisibilityStatus() { +function getVisibilityStatus(): Promise { return Promise.resolve(document.getElementById('splash') ? 'visible' : 'hidden'); } diff --git a/src/libs/BootSplash/types.ts b/src/libs/BootSplash/types.ts new file mode 100644 index 000000000000..2329d5315817 --- /dev/null +++ b/src/libs/BootSplash/types.ts @@ -0,0 +1,9 @@ +type VisibilityStatus = 'visible' | 'hidden'; + +type BootSplashModule = { + navigationBarHeight: number; + hide: () => Promise; + getVisibilityStatus: () => Promise; +}; + +export type {BootSplashModule, VisibilityStatus}; From a1dff6f28fb81b71fee4e898445e757cb96297a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arthur=20Gon=C3=A7alves?= Date: Thu, 21 Sep 2023 01:08:43 +0100 Subject: [PATCH 117/151] Addressed PR comments --- src/libs/BootSplash/index.native.ts | 5 ++--- src/libs/BootSplash/index.ts | 6 +++--- src/types/modules/react-native.d.ts | 5 +++++ 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/libs/BootSplash/index.native.ts b/src/libs/BootSplash/index.native.ts index f0082afa46d5..0790b4de89bc 100644 --- a/src/libs/BootSplash/index.native.ts +++ b/src/libs/BootSplash/index.native.ts @@ -1,10 +1,9 @@ import {NativeModules} from 'react-native'; import Log from '../Log'; -import {BootSplashModule} from './types'; -const BootSplash: BootSplashModule = NativeModules.BootSplash; +const BootSplash = NativeModules.BootSplash; -function hide() { +function hide(): Promise { Log.info('[BootSplash] hiding splash screen', false); return BootSplash.hide(); } diff --git a/src/libs/BootSplash/index.ts b/src/libs/BootSplash/index.ts index d614c06da090..24842fe631f4 100644 --- a/src/libs/BootSplash/index.ts +++ b/src/libs/BootSplash/index.ts @@ -1,11 +1,11 @@ import Log from '../Log'; import {VisibilityStatus} from './types'; -function resolveAfter(delay: number) { - return new Promise((resolve) => setTimeout(resolve, delay)); +function resolveAfter(delay: number): Promise { + return new Promise((resolve) => setTimeout(resolve, delay)); } -function hide() { +function hide(): Promise { Log.info('[BootSplash] hiding splash screen', false); return document.fonts.ready.then(() => { diff --git a/src/types/modules/react-native.d.ts b/src/types/modules/react-native.d.ts index 1b0b39e5f67d..ebe0974db690 100644 --- a/src/types/modules/react-native.d.ts +++ b/src/types/modules/react-native.d.ts @@ -1,9 +1,14 @@ /* eslint-disable @typescript-eslint/consistent-type-definitions */ import 'react-native'; +import {BootSplashModule} from '../../libs/BootSplash/types'; declare module 'react-native' { interface TextInput { // Typescript type declaration is missing in React Native for setting text selection. setSelection: (start: number, end: number) => void; } + + interface NativeModulesStatic { + BootSplash: BootSplashModule; + } } From 0d43181c955938fd331f8592d5221943aaa08e14 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Thu, 21 Sep 2023 08:47:04 +0530 Subject: [PATCH 118/151] fix: lint issues. Signed-off-by: Krishna Gupta --- src/components/HeaderWithBackButton/index.js | 4 ++-- src/components/PopoverMenu/index.js | 6 +++--- src/components/ThreeDotsMenu/index.js | 10 +++++----- src/pages/iou/WaypointEditor.js | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/components/HeaderWithBackButton/index.js b/src/components/HeaderWithBackButton/index.js index b99290918e13..26ed6d210b2f 100755 --- a/src/components/HeaderWithBackButton/index.js +++ b/src/components/HeaderWithBackButton/index.js @@ -47,7 +47,7 @@ function HeaderWithBackButton({ }, threeDotsMenuItems = [], children = null, - onPopoverHide = () => {}, + onModalHide = () => {}, }) { const [isDownloadButtonActive, temporarilyDisableDownloadButton] = useThrottledButtonState(); const {translate} = useLocalize(); @@ -138,7 +138,7 @@ function HeaderWithBackButton({ menuItems={threeDotsMenuItems} onIconPress={onThreeDotsButtonPress} anchorPosition={threeDotsAnchorPosition} - onPopoverHide={onPopoverHide} + onModalHide={onModalHide} /> )} {shouldShowCloseButton && ( diff --git a/src/components/PopoverMenu/index.js b/src/components/PopoverMenu/index.js index 1533f329ad4a..baead6578da9 100644 --- a/src/components/PopoverMenu/index.js +++ b/src/components/PopoverMenu/index.js @@ -36,7 +36,7 @@ const propTypes = { withoutOverlay: PropTypes.bool, /** Function to call on popover hide */ - onPopoverHide: PropTypes.func, + onModalHide: PropTypes.func, }; const defaultProps = { @@ -47,7 +47,7 @@ const defaultProps = { }, anchorRef: () => {}, withoutOverlay: false, - onPopoverHide: () => {}, + onModalHide: () => {}, }; function PopoverMenu(props) { @@ -82,7 +82,7 @@ function PopoverMenu(props) { isVisible={props.isVisible} onModalHide={() => { setFocusedIndex(-1); - props.onPopoverHide(); + props.onModalHide(); if (selectedItemIndex.current !== null) { props.menuItems[selectedItemIndex.current].onSelected(); selectedItemIndex.current = null; diff --git a/src/components/ThreeDotsMenu/index.js b/src/components/ThreeDotsMenu/index.js index 5daeb9669933..81d6cbd7b337 100644 --- a/src/components/ThreeDotsMenu/index.js +++ b/src/components/ThreeDotsMenu/index.js @@ -47,7 +47,7 @@ const propTypes = { }), /** Function to call on popover hide */ - onPopoverClose: PropTypes.func, + onModalHide: PropTypes.func, }; const defaultProps = { @@ -60,10 +60,10 @@ const defaultProps = { horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP, // we assume that popover menu opens below the button, anchor is at TOP }, - onPopoverHide: () => {}, + onModalHide: () => {}, }; -function ThreeDotsMenu({iconTooltip, icon, iconFill, iconStyles, onIconPress, menuItems, anchorPosition, anchorAlignment, onPopoverHide}) { +function ThreeDotsMenu({iconTooltip, icon, iconFill, iconStyles, onIconPress, menuItems, anchorPosition, anchorAlignment, onModalHide}) { const [isPopupMenuVisible, setPopupMenuVisible] = useState(false); const buttonRef = useRef(null); const {translate} = useLocalize(); @@ -74,7 +74,7 @@ function ThreeDotsMenu({iconTooltip, icon, iconFill, iconStyles, onIconPress, me const hidePopoverMenu = () => { InteractionManager.runAfterInteractions(() => { - onPopoverHide(); + onModalHide(); }); setPopupMenuVisible(false); }; @@ -108,7 +108,7 @@ function ThreeDotsMenu({iconTooltip, icon, iconFill, iconStyles, onIconPress, me setIsDeleteStopModalOpen(true), }, ]} - onPopoverHide={focus} + onModalHide={focus} /> Date: Thu, 21 Sep 2023 08:53:53 +0530 Subject: [PATCH 119/151] fix: minor comment fix. Signed-off-by: Krishna Gupta --- src/components/PopoverMenu/index.js | 2 +- src/components/ThreeDotsMenu/index.js | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/PopoverMenu/index.js b/src/components/PopoverMenu/index.js index baead6578da9..74d8c7fa8498 100644 --- a/src/components/PopoverMenu/index.js +++ b/src/components/PopoverMenu/index.js @@ -35,7 +35,7 @@ const propTypes = { withoutOverlay: PropTypes.bool, - /** Function to call on popover hide */ + /** Function to call on modal hide */ onModalHide: PropTypes.func, }; diff --git a/src/components/ThreeDotsMenu/index.js b/src/components/ThreeDotsMenu/index.js index 218967aa2073..5e22a74fa37e 100644 --- a/src/components/ThreeDotsMenu/index.js +++ b/src/components/ThreeDotsMenu/index.js @@ -46,7 +46,7 @@ const propTypes = { vertical: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_VERTICAL)), }), - /** Function to call on popover hide */ + /** Function to call on modal hide */ onModalHide: PropTypes.func, /** Whether the popover menu should overlay the current view */ @@ -67,8 +67,7 @@ const defaultProps = { shouldOverlay: false, }; - -function ThreeDotsMenu({iconTooltip, icon, iconFill, iconStyles, onIconPress, menuItems, anchorPosition, anchorAlignment,onModalHide, shouldOverlay}) { +function ThreeDotsMenu({iconTooltip, icon, iconFill, iconStyles, onIconPress, menuItems, anchorPosition, anchorAlignment, onModalHide, shouldOverlay}) { const [isPopupMenuVisible, setPopupMenuVisible] = useState(false); const buttonRef = useRef(null); const {translate} = useLocalize(); From 6dd360142723c1725b7a81f59eaafdb31de9bc49 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Thu, 21 Sep 2023 08:55:54 +0530 Subject: [PATCH 120/151] fix: lint issue if statement. Signed-off-by: Krishna Gupta --- src/pages/iou/WaypointEditor.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pages/iou/WaypointEditor.js b/src/pages/iou/WaypointEditor.js index 239cfe33153f..5eb17aac1b50 100644 --- a/src/pages/iou/WaypointEditor.js +++ b/src/pages/iou/WaypointEditor.js @@ -159,7 +159,9 @@ function WaypointEditor({transactionID, route: {params: {iouType = '', waypointI const focus = () => { InteractionManager.runAfterInteractions(() => { - if (!textInput.current) return; + if (!textInput.current) { + return; + } textInput.current.focus(); }); }; From 9514e235181848d56aa45b8df618750b54b3e1fe Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Thu, 21 Sep 2023 09:33:00 +0530 Subject: [PATCH 121/151] fix: input focus function name. Signed-off-by: Krishna Gupta --- src/pages/iou/WaypointEditor.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/iou/WaypointEditor.js b/src/pages/iou/WaypointEditor.js index 5eb17aac1b50..3f089ed5f115 100644 --- a/src/pages/iou/WaypointEditor.js +++ b/src/pages/iou/WaypointEditor.js @@ -157,7 +157,7 @@ function WaypointEditor({transactionID, route: {params: {iouType = '', waypointI Navigation.goBack(ROUTES.getMoneyRequestDistanceTabRoute(iouType)); }; - const focus = () => { + const focusAddressInput = () => { InteractionManager.runAfterInteractions(() => { if (!textInput.current) { return; @@ -188,14 +188,14 @@ function WaypointEditor({transactionID, route: {params: {iouType = '', waypointI onSelected: () => setIsDeleteStopModalOpen(true), }, ]} - onModalHide={focus} + onModalHide={focusAddressInput} /> setIsDeleteStopModalOpen(false)} - onModalHide={focus} + onModalHide={focusAddressInput} prompt={translate('distance.deleteWaypointConfirmation')} confirmText={translate('common.delete')} cancelText={translate('common.cancel')} From 74871ced375e8af817c6e09f4a0b5af50598af1f Mon Sep 17 00:00:00 2001 From: Yonathan Evan Christy Date: Thu, 21 Sep 2023 11:13:50 +0700 Subject: [PATCH 122/151] Update package.json for eslint-config-expensify 2.0.39 --- package-lock.json | 14 +++++++------- package.json | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index e00218860c30..5980724e7432 100644 --- a/package-lock.json +++ b/package-lock.json @@ -190,7 +190,7 @@ "electron-builder": "24.6.4", "eslint": "^7.6.0", "eslint-config-airbnb-typescript": "^17.1.0", - "eslint-config-expensify": "^2.0.38", + "eslint-config-expensify": "^2.0.39", "eslint-config-prettier": "^8.8.0", "eslint-plugin-jest": "^24.1.0", "eslint-plugin-jsdoc": "^46.2.6", @@ -26492,9 +26492,9 @@ } }, "node_modules/eslint-config-expensify": { - "version": "2.0.38", - "resolved": "https://registry.npmjs.org/eslint-config-expensify/-/eslint-config-expensify-2.0.38.tgz", - "integrity": "sha512-jAlrYSjkDV8YESUUPcaTjUM8Fgru+37FS+Hq6zzcRR0FbA5bLiOPguhJHo77XpYh5N+PEf4wrpgsS04sjdgDPg==", + "version": "2.0.39", + "resolved": "https://registry.npmjs.org/eslint-config-expensify/-/eslint-config-expensify-2.0.39.tgz", + "integrity": "sha512-DIxR3k99ZIDPE2NK+WLLRWpoDq06gTXdY8XZg9Etd1UqZ7fXm/Yz3/QkTxu7CH7UaXbCH3P4PTo023ULQGKOSw==", "dev": true, "dependencies": { "@lwc/eslint-plugin-lwc": "^0.11.0", @@ -67264,9 +67264,9 @@ } }, "eslint-config-expensify": { - "version": "2.0.38", - "resolved": "https://registry.npmjs.org/eslint-config-expensify/-/eslint-config-expensify-2.0.38.tgz", - "integrity": "sha512-jAlrYSjkDV8YESUUPcaTjUM8Fgru+37FS+Hq6zzcRR0FbA5bLiOPguhJHo77XpYh5N+PEf4wrpgsS04sjdgDPg==", + "version": "2.0.39", + "resolved": "https://registry.npmjs.org/eslint-config-expensify/-/eslint-config-expensify-2.0.39.tgz", + "integrity": "sha512-DIxR3k99ZIDPE2NK+WLLRWpoDq06gTXdY8XZg9Etd1UqZ7fXm/Yz3/QkTxu7CH7UaXbCH3P4PTo023ULQGKOSw==", "dev": true, "requires": { "@lwc/eslint-plugin-lwc": "^0.11.0", diff --git a/package.json b/package.json index 8e0b493b495e..df1448ee7be6 100644 --- a/package.json +++ b/package.json @@ -61,9 +61,9 @@ "@formatjs/intl-pluralrules": "^5.2.2", "@gorhom/portal": "^1.0.14", "@invertase/react-native-apple-authentication": "^2.2.2", - "@oguzhnatly/react-native-image-manipulator": "github:Expensify/react-native-image-manipulator#5cdae3d4455b03a04c57f50be3863e2fe6c92c52", "@kie/act-js": "^2.0.1", "@kie/mock-github": "^1.0.0", + "@oguzhnatly/react-native-image-manipulator": "github:Expensify/react-native-image-manipulator#5cdae3d4455b03a04c57f50be3863e2fe6c92c52", "@onfido/react-native-sdk": "7.4.0", "@react-native-async-storage/async-storage": "^1.17.10", "@react-native-camera-roll/camera-roll": "5.4.0", @@ -81,9 +81,9 @@ "@react-navigation/stack": "6.3.16", "@react-ng/bounds-observer": "^0.2.1", "@rnmapbox/maps": "^10.0.11", + "@types/node": "^18.14.0", "@ua/react-native-airship": "^15.2.6", "awesome-phonenumber": "^5.4.0", - "@types/node": "^18.14.0", "babel-plugin-transform-remove-console": "^6.9.4", "babel-polyfill": "^6.26.0", "canvas-size": "^1.2.6", @@ -111,8 +111,8 @@ "react-collapse": "^5.1.0", "react-content-loader": "^6.1.0", "react-dom": "18.1.0", - "react-map-gl": "^7.1.3", "react-error-boundary": "^4.0.11", + "react-map-gl": "^7.1.3", "react-native": "0.72.4", "react-native-blob-util": "^0.17.3", "react-native-collapsible": "^1.6.0", From 6b70fff40a3e50537cb96bc29b6d5dae2fcea377 Mon Sep 17 00:00:00 2001 From: Yonathan Evan Christy Date: Thu, 21 Sep 2023 11:23:00 +0700 Subject: [PATCH 123/151] Revert package.json order after Update for eslint-config-expensify 2.0.39 --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 7bad0e3bd5db..8dd61d44c43d 100644 --- a/package.json +++ b/package.json @@ -61,9 +61,9 @@ "@formatjs/intl-pluralrules": "^5.2.2", "@gorhom/portal": "^1.0.14", "@invertase/react-native-apple-authentication": "^2.2.2", + "@oguzhnatly/react-native-image-manipulator": "github:Expensify/react-native-image-manipulator#5cdae3d4455b03a04c57f50be3863e2fe6c92c52", "@kie/act-js": "^2.0.1", "@kie/mock-github": "^1.0.0", - "@oguzhnatly/react-native-image-manipulator": "github:Expensify/react-native-image-manipulator#5cdae3d4455b03a04c57f50be3863e2fe6c92c52", "@onfido/react-native-sdk": "7.4.0", "@react-native-async-storage/async-storage": "^1.17.10", "@react-native-camera-roll/camera-roll": "5.4.0", @@ -81,9 +81,9 @@ "@react-navigation/stack": "6.3.16", "@react-ng/bounds-observer": "^0.2.1", "@rnmapbox/maps": "^10.0.11", - "@types/node": "^18.14.0", "@ua/react-native-airship": "^15.2.6", "awesome-phonenumber": "^5.4.0", + "@types/node": "^18.14.0", "babel-plugin-transform-remove-console": "^6.9.4", "babel-polyfill": "^6.26.0", "canvas-size": "^1.2.6", @@ -111,8 +111,8 @@ "react-collapse": "^5.1.0", "react-content-loader": "^6.1.0", "react-dom": "18.1.0", - "react-error-boundary": "^4.0.11", "react-map-gl": "^7.1.3", + "react-error-boundary": "^4.0.11", "react-native": "0.72.4", "react-native-blob-util": "^0.17.3", "react-native-collapsible": "^1.6.0", From e9435a9e359d19b7798032951efcb18ed9abf408 Mon Sep 17 00:00:00 2001 From: Yonathan Evan Christy Date: Thu, 21 Sep 2023 11:32:57 +0700 Subject: [PATCH 124/151] Update fix lint --- src/pages/iou/MoneyRequestTagPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/MoneyRequestTagPage.js b/src/pages/iou/MoneyRequestTagPage.js index 781b405e0774..3f31f39f4b7b 100644 --- a/src/pages/iou/MoneyRequestTagPage.js +++ b/src/pages/iou/MoneyRequestTagPage.js @@ -109,7 +109,7 @@ export default compose( key: ONYXKEYS.IOU, }, }), - // eslint-disable-next-line + // eslint-disable-next-line withOnyx({ report: { // Fetch report ID from IOU participants if no report ID is set in route From 6d9c7958827a00001e8422e33116738b7b2ad60e Mon Sep 17 00:00:00 2001 From: Yonathan Evan Christy Date: Thu, 21 Sep 2023 12:20:12 +0700 Subject: [PATCH 125/151] Revert added deleted withOnyx --- src/pages/iou/MoneyRequestTagPage.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/pages/iou/MoneyRequestTagPage.js b/src/pages/iou/MoneyRequestTagPage.js index 3f31f39f4b7b..d899f6147f63 100644 --- a/src/pages/iou/MoneyRequestTagPage.js +++ b/src/pages/iou/MoneyRequestTagPage.js @@ -110,13 +110,6 @@ export default compose( }, }), // eslint-disable-next-line - withOnyx({ - report: { - // Fetch report ID from IOU participants if no report ID is set in route - key: ({route, iou}) => `${ONYXKEYS.COLLECTION.REPORT}${lodashGet(route, 'params.reportID', '') || lodashGet(iou, 'participants.0.reportID', '')}`, - }, - }), - // eslint-disable-next-line withOnyx({ policyTags: { key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report ? report.policyID : '0'}`, From 9ebea36d78eea920c5703dfe19d8bf6b4a338ec2 Mon Sep 17 00:00:00 2001 From: Yonathan Evan Christy Date: Thu, 21 Sep 2023 12:29:57 +0700 Subject: [PATCH 126/151] Update `// eslint-disable-next-line` to `// eslint-disable-next-line rulesdir/no-multiple-onyx-in-file` --- src/components/LHNOptionsList/OptionRowLHNData.js | 4 ++-- src/pages/EditRequestPage.js | 2 +- src/pages/iou/MoneyRequestTagPage.js | 2 +- src/pages/iou/steps/MoneyRequestConfirmPage.js | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/LHNOptionsList/OptionRowLHNData.js b/src/components/LHNOptionsList/OptionRowLHNData.js index 37bc4df9ea3a..4eebdd387fab 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.js +++ b/src/components/LHNOptionsList/OptionRowLHNData.js @@ -195,7 +195,7 @@ export default React.memo( key: ONYXKEYS.NVP_PREFERRED_LOCALE, }, }), - // eslint-disable-next-line + // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file withOnyx({ parentReportActions: { key: ({fullReport}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${fullReport.parentReportID}`, @@ -210,7 +210,7 @@ export default React.memo( // However, performance overhead of this is minimized by using memos inside the component. receiptTransactions: {key: ONYXKEYS.COLLECTION.TRANSACTION}, }), - // eslint-disable-next-line + // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file withOnyx({ transaction: { key: ({fullReport, parentReportActions}) => diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index 466c5e1a1df3..d3b1df1d4e9b 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -194,7 +194,7 @@ export default compose( key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.threadReportID}`, }, }), - // eslint-disable-next-line + // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file withOnyx({ parentReport: { key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT}${report ? report.parentReportID : '0'}`, diff --git a/src/pages/iou/MoneyRequestTagPage.js b/src/pages/iou/MoneyRequestTagPage.js index d899f6147f63..60be6035431c 100644 --- a/src/pages/iou/MoneyRequestTagPage.js +++ b/src/pages/iou/MoneyRequestTagPage.js @@ -109,7 +109,7 @@ export default compose( key: ONYXKEYS.IOU, }, }), - // eslint-disable-next-line + // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file withOnyx({ policyTags: { key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report ? report.policyID : '0'}`, diff --git a/src/pages/iou/steps/MoneyRequestConfirmPage.js b/src/pages/iou/steps/MoneyRequestConfirmPage.js index 4f1ae106623f..14a6cc8a7c23 100644 --- a/src/pages/iou/steps/MoneyRequestConfirmPage.js +++ b/src/pages/iou/steps/MoneyRequestConfirmPage.js @@ -353,7 +353,7 @@ export default compose( key: ONYXKEYS.IOU, }, }), - // eslint-disable-next-line + // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file withOnyx({ report: { key: ({route, iou}) => { @@ -373,7 +373,7 @@ export default compose( key: `${ONYXKEYS.COLLECTION.SELECTED_TAB}${CONST.TAB.RECEIPT_TAB_ID}`, }, }), - // eslint-disable-next-line + // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file withOnyx({ policy: { key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`, From dc650be3214da7ede698ca7cb3056ebbe9d5769e Mon Sep 17 00:00:00 2001 From: Hans Date: Thu, 21 Sep 2023 13:24:05 +0700 Subject: [PATCH 127/151] Fix loading overlay header for PrivateNotes page --- src/pages/PrivateNotes/PrivateNotesListPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/PrivateNotes/PrivateNotesListPage.js b/src/pages/PrivateNotes/PrivateNotesListPage.js index 098bfd2a245b..6c0664d80fa2 100644 --- a/src/pages/PrivateNotes/PrivateNotesListPage.js +++ b/src/pages/PrivateNotes/PrivateNotesListPage.js @@ -125,7 +125,7 @@ function PrivateNotesListPage({report, personalDetailsList, network, session}) { onCloseButtonPress={() => Navigation.dismissModal()} /> {report.isLoadingPrivateNotes && _.isEmpty(lodashGet(report, 'privateNotes', {})) ? ( - + ) : ( _.map(privateNotes, (item, index) => getMenuItem(item, index)) )} From 977e79d57c2893883e822563afe7a0ec107dbc92 Mon Sep 17 00:00:00 2001 From: Yonathan Evan Christy Date: Thu, 21 Sep 2023 13:32:09 +0700 Subject: [PATCH 128/151] Update .eslintrc.js --- .eslintrc.js | 1 - 1 file changed, 1 deletion(-) diff --git a/.eslintrc.js b/.eslintrc.js index 267ec23b8674..3c144064eb62 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -39,7 +39,6 @@ module.exports = { files: ['*.js', '*.jsx', '*.ts', '*.tsx'], rules: { 'rulesdir/onyx-props-must-have-default': 'off', - 'rulesdir/no-multiple-onyx-in-file': 'error', 'react-native-a11y/has-accessibility-hint': ['off'], 'react-native-a11y/has-valid-accessibility-descriptors': [ 'error', From ecca4974e395322e049379ec08d5e853d18beab4 Mon Sep 17 00:00:00 2001 From: hurali97 Date: Thu, 21 Sep 2023 12:26:14 +0500 Subject: [PATCH 129/151] fix: hover not shown when scroll on same item --- src/components/Hoverable/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/Hoverable/index.js b/src/components/Hoverable/index.js index 7dd918f15cf4..38ea64952a2c 100644 --- a/src/components/Hoverable/index.js +++ b/src/components/Hoverable/index.js @@ -46,7 +46,6 @@ class Hoverable extends Component { * If the user has started scrolling and the isHoveredRef is true, then we should set the hover state to false. * This is to hide the existing hover and reaction bar. */ - this.isHoveredRef = false; this.setState({isHovered: false}, this.props.onHoverOut); } this.isScrollingRef = scrolling; From 2c1608392cf2f5b73520031659cf0f59e7a6721e Mon Sep 17 00:00:00 2001 From: Janic Duplessis Date: Thu, 21 Sep 2023 10:38:39 +0200 Subject: [PATCH 130/151] Set batch size to 1 only on web --- src/components/InvertedFlatList/BaseInvertedFlatList.js | 4 ---- src/components/InvertedFlatList/index.js | 3 +++ 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/InvertedFlatList/BaseInvertedFlatList.js b/src/components/InvertedFlatList/BaseInvertedFlatList.js index 0327e4e0b5b9..0bfffb733052 100644 --- a/src/components/InvertedFlatList/BaseInvertedFlatList.js +++ b/src/components/InvertedFlatList/BaseInvertedFlatList.js @@ -136,10 +136,6 @@ class BaseInvertedFlatList extends Component { // Native platforms do not need to measure items and work fine without this. // Web requires that items be measured or else crazy things happen when scrolling. getItemLayout={this.props.shouldMeasureItems ? this.getItemLayout : undefined} - // Keep batch size relatively small for responsiveness, but not too small as it will cause - // excessive rendering. See https://github.com/Expensify/App/pull/19345 for performance testing - // of this value. - maxToRenderPerBatch={10} windowSize={15} // Commenting the line below as it breaks the unread indicator test diff --git a/src/components/InvertedFlatList/index.js b/src/components/InvertedFlatList/index.js index 74409e9a0fe0..d46cd5801605 100644 --- a/src/components/InvertedFlatList/index.js +++ b/src/components/InvertedFlatList/index.js @@ -119,6 +119,9 @@ function InvertedFlatList(props) { shouldMeasureItems contentContainerStyle={StyleSheet.compose(contentContainerStyle, styles.justifyContentEnd)} onScroll={handleScroll} + // We need to keep batch size to one to workaround a bug in react-native-web. + // This can be removed once https://github.com/Expensify/App/pull/24482 is merged. + maxToRenderPerBatch={1} /> ); } From f19d5ab1216bc03360c5c4eeebb82ca70b9ff1e5 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Thu, 21 Sep 2023 10:52:24 +0200 Subject: [PATCH 131/151] Add curly option to ESLint config for Typescript --- .eslintrc.js | 5 +++-- src/components/MapView/MapView.tsx | 4 +++- src/libs/Growl.ts | 8 ++++++-- src/libs/isInputAutoFilled.ts | 4 +++- src/libs/requireParameters.ts | 4 +++- 5 files changed, 18 insertions(+), 7 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 3c144064eb62..b5b4add538f6 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -24,7 +24,7 @@ const restrictedImportPatterns = [ ]; module.exports = { - extends: ['expensify', 'plugin:storybook/recommended', 'plugin:react-hooks/recommended', 'prettier', 'plugin:react-native-a11y/basic'], + extends: ['expensify', 'plugin:storybook/recommended', 'plugin:react-hooks/recommended', 'plugin:react-native-a11y/basic', 'prettier'], plugins: ['react-hooks', 'react-native-a11y'], parser: 'babel-eslint', ignorePatterns: ['!.*', 'src/vendor', '.github/actions/**/index.js', 'desktop/dist/*.js', 'dist/*.js', 'node_modules/.bin/**', 'node_modules/.cache/**', '.git/**'], @@ -46,7 +46,6 @@ module.exports = { touchables: ['PressableWithoutFeedback', 'PressableWithFeedback'], }, ], - curly: 'error', }, }, { @@ -76,6 +75,7 @@ module.exports = { patterns: restrictedImportPatterns, }, ], + curly: 'error', }, }, { @@ -162,6 +162,7 @@ module.exports = { patterns: restrictedImportPatterns, }, ], + curly: 'error', }, }, { diff --git a/src/components/MapView/MapView.tsx b/src/components/MapView/MapView.tsx index 7a2248ffafb9..d9f51e111a43 100644 --- a/src/components/MapView/MapView.tsx +++ b/src/components/MapView/MapView.tsx @@ -54,7 +54,9 @@ const MapView = forwardRef(({accessToken, style, ma }, [accessToken]); const setMapIdle = (e: MapState) => { - if (e.gestures.isGestureActive) return; + if (e.gestures.isGestureActive) { + return; + } setIsIdle(true); if (onMapReady) { onMapReady(); diff --git a/src/libs/Growl.ts b/src/libs/Growl.ts index 99c728f0a210..33d7311973cb 100644 --- a/src/libs/Growl.ts +++ b/src/libs/Growl.ts @@ -12,7 +12,9 @@ const isReadyPromise = new Promise((resolve) => { }); function setIsReady() { - if (!resolveIsReadyPromise) return; + if (!resolveIsReadyPromise) { + return; + } resolveIsReadyPromise(); } @@ -21,7 +23,9 @@ function setIsReady() { */ function show(bodyText: string, type: string, duration: number = CONST.GROWL.DURATION) { isReadyPromise.then(() => { - if (!growlRef?.current?.show) return; + if (!growlRef?.current?.show) { + return; + } growlRef.current.show(bodyText, type, duration); }); } diff --git a/src/libs/isInputAutoFilled.ts b/src/libs/isInputAutoFilled.ts index 0abe634001e4..e1b9942b0e78 100644 --- a/src/libs/isInputAutoFilled.ts +++ b/src/libs/isInputAutoFilled.ts @@ -4,7 +4,9 @@ import isSelectorSupported from './isSelectorSupported'; * Check the input is auto filled or not */ export default function isInputAutoFilled(input: Element): boolean { - if (!input?.matches) return false; + if (!input?.matches) { + return false; + } if (isSelectorSupported(':autofill')) { return input.matches(':-webkit-autofill') || input.matches(':autofill'); } diff --git a/src/libs/requireParameters.ts b/src/libs/requireParameters.ts index 098a6d114430..ebeb55e254e0 100644 --- a/src/libs/requireParameters.ts +++ b/src/libs/requireParameters.ts @@ -14,7 +14,9 @@ export default function requireParameters(parameterNames: string[], parameters: const propertiesToRedact = ['authToken', 'password', 'partnerUserSecret', 'twoFactorAuthCode']; const parametersCopy = {...parameters}; Object.keys(parametersCopy).forEach((key) => { - if (!propertiesToRedact.includes(key.toString())) return; + if (!propertiesToRedact.includes(key.toString())) { + return; + } parametersCopy[key] = ''; }); From 8753fa362c0889c3356e58936cb3f89b6a64dbd1 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Thu, 21 Sep 2023 11:56:50 +0200 Subject: [PATCH 132/151] update name of a method --- tests/unit/OptionsListUtilsTest.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/OptionsListUtilsTest.js b/tests/unit/OptionsListUtilsTest.js index 6bc8b1b01528..6f20e48835fd 100644 --- a/tests/unit/OptionsListUtilsTest.js +++ b/tests/unit/OptionsListUtilsTest.js @@ -1024,7 +1024,7 @@ describe('OptionsListUtils', () => { ); expect(largeWrongSearchResult.categoryOptions).toStrictEqual(largeWrongSearchResultList); - const emptyResult = OptionsListUtils.getNewChatOptions(REPORTS, PERSONAL_DETAILS, [], search, selectedOptions, [], false, false, true, emptyCategoriesList); + const emptyResult = OptionsListUtils.getFilteredOptions(REPORTS, PERSONAL_DETAILS, [], search, selectedOptions, [], false, false, true, emptyCategoriesList); expect(emptyResult.categoryOptions).toStrictEqual(emptySelectedResultList); }); From 0b2c77ea79d5e36eb568c754dd7f530cd31a69ff Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 21 Sep 2023 15:39:59 +0530 Subject: [PATCH 133/151] show platforms in homepage --- docs/_includes/platform.html | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 docs/_includes/platform.html diff --git a/docs/_includes/platform.html b/docs/_includes/platform.html new file mode 100644 index 000000000000..f3867ee4f5b7 --- /dev/null +++ b/docs/_includes/platform.html @@ -0,0 +1,18 @@ +{% assign selectedPlatform = page.url | remove: "/hubs/" | remove: "/" | remove: ".html" %} +{% assign platform = site.data.routes.platforms | where: "href", selectedPlatform | first %} +
+

{{ platform.hub-title }}

+ +

{{ site.data.routes.home.description }}

+ +
+ {% for hub in platform.hubs %} + {% include hub-card.html hub=hub platform=selectedPlatform %} + {% endfor %} +
+ +
+ + {% include floating-concierge-button.html id="floating-concierge-button-global" %} +
+
From a56668fa12d6c47140023d14ca0d5f796949a474 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 21 Sep 2023 15:42:05 +0530 Subject: [PATCH 134/151] show platforms in homepage --- docs/index.html | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/docs/index.html b/docs/index.html index 74296c200971..70bd5f31545a 100644 --- a/docs/index.html +++ b/docs/index.html @@ -6,15 +6,11 @@

{{ site.data.routes.home.title }}

{{ site.data.routes.home.description }}

-

- Which best describes how you use Expensify? -

-
- {% include hub-card.html href="split-bills" %} - {% include hub-card.html href="request-money" %} - {% include hub-card.html href="playbooks" %} - {% include hub-card.html href="other" %} + {% for platform in site.data.routes.platforms %} + {% assign platform_href = platform.href %} + {% include platform-card.html href=platform.platform_href %} + {% endfor %}
From 08a3bae8fc503b8d0412861bf0361fde9ccde3bb Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 21 Sep 2023 15:42:23 +0530 Subject: [PATCH 135/151] add platform cards --- docs/_includes/platform-card.html | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 docs/_includes/platform-card.html diff --git a/docs/_includes/platform-card.html b/docs/_includes/platform-card.html new file mode 100644 index 000000000000..d56a234a5c14 --- /dev/null +++ b/docs/_includes/platform-card.html @@ -0,0 +1,13 @@ +{% assign platform = site.data.routes.platforms | where: "href", include.href | first %} + + +
+
+ {{ platform.href }} +
+
+

{{ platform.title }}

+

{{ platform.description }}

+
+
+
From 00492272c2bb7734666fe6cda4654492cfe2c5e8 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 21 Sep 2023 15:44:32 +0530 Subject: [PATCH 136/151] show platform sepecific articles --- docs/_includes/article-card.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_includes/article-card.html b/docs/_includes/article-card.html index a088e5e406db..b6d8998c13ef 100644 --- a/docs/_includes/article-card.html +++ b/docs/_includes/article-card.html @@ -1,4 +1,4 @@ - +

{{ include.title }}

From e157b28b9dc676ef7d2ac9454355fd65fd8d9abf Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 21 Sep 2023 15:46:23 +0530 Subject: [PATCH 137/151] show platform specific hubs --- docs/_includes/hub-card.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/_includes/hub-card.html b/docs/_includes/hub-card.html index 36bf3bc36e6a..b5188bda7670 100644 --- a/docs/_includes/hub-card.html +++ b/docs/_includes/hub-card.html @@ -1,6 +1,6 @@ -{% assign hub = site.data.routes.hubs | where: "href", include.href | first %} - -
+{% assign hub = include.hub %} +{% assign platform = include.platform %} +
{{ hub.href }} From 47087a7723308cf7a3989b288bb5d930cde7dc3e Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 21 Sep 2023 15:49:23 +0530 Subject: [PATCH 138/151] show platform specific articles --- docs/_includes/hub.html | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/docs/_includes/hub.html b/docs/_includes/hub.html index acdc901a38f6..6b0b0e590b19 100644 --- a/docs/_includes/hub.html +++ b/docs/_includes/hub.html @@ -1,5 +1,8 @@ -{% assign activeHub = page.url | remove: "/hubs/" | remove: ".html" %} -{% assign hub = site.data.routes.hubs | where: "href", activeHub | first %} +{% assign activePlatform = page.url | replace: '/', ' ' | truncatewords: 1 | remove:'...' %} +{% assign platform = site.data.routes.platforms | where: "href", activePlatform | first %} + +{% assign activeHub = page.url | remove: activePlatform | remove: "/hubs/" | remove: "/" | remove: ".html" %} +{% assign hub = platform.hubs | where: "href", activeHub | first %}

{{ hub.title }} @@ -9,6 +12,16 @@

{{ hub.description }}

+{% if hub.articles %} +
+
+ {% for article in hub.articles %} + {% include article-card.html hub=hub.href href=article.href title=article.title platform=activePlatform %} + {% endfor %} +
+
+{% endif %} + {% for section in hub.sections %}

@@ -18,18 +31,8 @@

{% for article in section.articles %} {% assign article_href = section.href | append: '/' | append: article.href %} - {% include article-card.html hub=hub.href href=article_href title=article.title %} + {% include article-card.html hub=hub.href href=article_href title=article.title platform=activePlatform %} {% endfor %}

{% endfor %} - -{% if hub.articles %} -
-
- {% for article in hub.articles %} - {% include article-card.html hub=hub.href href=article.href title=article.title %} - {% endfor %} -
-
-{% endif %} From ab47aca3e52d77b969fd4df592a149c541e4f09a Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 21 Sep 2023 15:51:25 +0530 Subject: [PATCH 139/151] show platforms in LHN --- docs/_includes/lhn-article-link.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_includes/lhn-article-link.html b/docs/_includes/lhn-article-link.html index f9c4f31f0dbe..91c0de4aacce 100644 --- a/docs/_includes/lhn-article-link.html +++ b/docs/_includes/lhn-article-link.html @@ -1,5 +1,5 @@
  • - + {{ include.title }}
  • From f3a43678f23c0f0113bf0369af89901b5758bc43 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 21 Sep 2023 15:52:13 +0530 Subject: [PATCH 140/151] show hubs and articles in LHN --- docs/_includes/lhn-template.html | 61 ++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/docs/_includes/lhn-template.html b/docs/_includes/lhn-template.html index 0473e5da9e9c..015c8211e5b2 100644 --- a/docs/_includes/lhn-template.html +++ b/docs/_includes/lhn-template.html @@ -1,4 +1,5 @@ -{% assign activeHub = page.url | remove: "/hubs/" | remove: ".html" %} +{% assign activePlatform = page.url | replace:'/',' ' | truncatewords: 1 | remove:'...' %} +{% assign activeHub = page.url | remove: activePlatform | remove: "/hubs/" | remove: "/" | remove: ".html" %}
      - {% for hub in site.data.routes.hubs %} - {% if hub.href == activeHub %} + {% for platform in site.data.routes.platforms %} + {% if platform.href == activePlatform %}
    • -
        - - {% for section in hub.sections %} + {% for hub in platform.hubs %} +
          + {% if hub.href == activeHub %} + +
            + {% for article in hub.articles %} + {% include lhn-article-link.html platform=activePlatform hub=hub.href href=article.href title=article.title %} + {% endfor %} + + {% for section in hub.sections %} +
          • + {{ section.title }} +
              + {% for article in section.articles %} + {% assign article_href = section.href | append: '/' | append: article.href %} + {% include lhn-article-link.html platform=activePlatform hub=hub.href href=article_href title=article.title %} + {% endfor %} +
            +
          • + {% endfor %} +
          + {% else %}
        • - {{ section.title }} -
            - {% for article in section.articles %} - {% assign article_href = section.href | append: '/' | append: article.href %} - {% include lhn-article-link.html hub=hub.href href=article_href title=article.title %} - {% endfor %} -
          + + + {{ hub.title }} +
        • - {% endfor %} - - - {% for article in hub.articles %} - {% include lhn-article-link.html hub=hub.href href=article.href title=article.title %} - {% endfor %} + {% endif %}
        + {% endfor %} + {% else %}
      • - + - {{ hub.title }} + {{ platform.title }}
      • {% endif %} From c68317dbd6527504b67b1a4bf5af5c80758ceb4a Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 21 Sep 2023 15:55:02 +0530 Subject: [PATCH 141/151] fix: deeplinking and pressing back --- docs/_layouts/default.html | 7 ------- docs/assets/js/main.js | 8 ++++---- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html index 209d14de0f48..de3fbc203243 100644 --- a/docs/_layouts/default.html +++ b/docs/_layouts/default.html @@ -96,12 +96,5 @@

        Didn't find what you were looking for?

        {% include footer.html %}

    - - - diff --git a/docs/assets/js/main.js b/docs/assets/js/main.js index f0f335536c20..aebd0f5d4864 100644 --- a/docs/assets/js/main.js +++ b/docs/assets/js/main.js @@ -58,10 +58,10 @@ function navigateBack() { return; } - const hubs = JSON.parse(document.getElementById('hubs-data').value); - const hubToNavigate = hubs.find((hub) => window.location.pathname.includes(hub)); // eslint-disable-line rulesdir/prefer-underscore-method - if (hubToNavigate) { - window.location.href = `/hubs/${hubToNavigate}`; + // Path name is of the form /articles/[platform]/[hub]/[resource] + const path = window.location.pathname.split('/'); + if (path[2] && path[3]) { + window.location.href = `/${path[2]}/hubs/${path[3]}`; } else { window.location.href = '/'; } From 7980860a235f32174a462466dfe45e6fe4d69973 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 21 Sep 2023 16:00:10 +0530 Subject: [PATCH 142/151] add icons for hubs --- docs/assets/images/accounting.svg | 68 +++++++++++++ docs/assets/images/bank-card.svg | 41 ++++++++ docs/assets/images/envelope-receipt.svg | 35 +++++++ docs/assets/images/gears.svg | 101 +++++++++++++++++++ docs/assets/images/hand-card.svg | 19 ++++ docs/assets/images/money-into-wallet.svg | 32 +++++++ docs/assets/images/money-receipt.svg | 34 +++++++ docs/assets/images/money-wings.svg | 26 +++++ docs/assets/images/monitor.svg | 12 +++ docs/assets/images/workflow.svg | 117 +++++++++++++++++++++++ 10 files changed, 485 insertions(+) create mode 100644 docs/assets/images/accounting.svg create mode 100644 docs/assets/images/bank-card.svg create mode 100644 docs/assets/images/envelope-receipt.svg create mode 100644 docs/assets/images/gears.svg create mode 100644 docs/assets/images/hand-card.svg create mode 100644 docs/assets/images/money-into-wallet.svg create mode 100644 docs/assets/images/money-receipt.svg create mode 100644 docs/assets/images/money-wings.svg create mode 100644 docs/assets/images/monitor.svg create mode 100644 docs/assets/images/workflow.svg diff --git a/docs/assets/images/accounting.svg b/docs/assets/images/accounting.svg new file mode 100644 index 000000000000..4398e9573747 --- /dev/null +++ b/docs/assets/images/accounting.svg @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/images/bank-card.svg b/docs/assets/images/bank-card.svg new file mode 100644 index 000000000000..48da9af0d986 --- /dev/null +++ b/docs/assets/images/bank-card.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/images/envelope-receipt.svg b/docs/assets/images/envelope-receipt.svg new file mode 100644 index 000000000000..40f57cc4ebda --- /dev/null +++ b/docs/assets/images/envelope-receipt.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/images/gears.svg b/docs/assets/images/gears.svg new file mode 100644 index 000000000000..23621afc8008 --- /dev/null +++ b/docs/assets/images/gears.svg @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/images/hand-card.svg b/docs/assets/images/hand-card.svg new file mode 100644 index 000000000000..779e6ff4184c --- /dev/null +++ b/docs/assets/images/hand-card.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/images/money-into-wallet.svg b/docs/assets/images/money-into-wallet.svg new file mode 100644 index 000000000000..d6d5b0e7d6e7 --- /dev/null +++ b/docs/assets/images/money-into-wallet.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/images/money-receipt.svg b/docs/assets/images/money-receipt.svg new file mode 100644 index 000000000000..379d56727e42 --- /dev/null +++ b/docs/assets/images/money-receipt.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/images/money-wings.svg b/docs/assets/images/money-wings.svg new file mode 100644 index 000000000000..c2155080f721 --- /dev/null +++ b/docs/assets/images/money-wings.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/images/monitor.svg b/docs/assets/images/monitor.svg new file mode 100644 index 000000000000..6e2580b4c9e8 --- /dev/null +++ b/docs/assets/images/monitor.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/docs/assets/images/workflow.svg b/docs/assets/images/workflow.svg new file mode 100644 index 000000000000..e5eac423cd1d --- /dev/null +++ b/docs/assets/images/workflow.svg @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From b123a7699cd65503f9f9e7d305725772fbee8fdf Mon Sep 17 00:00:00 2001 From: Huzaifa Rasheed Date: Thu, 21 Sep 2023 16:34:41 +0500 Subject: [PATCH 143/151] Allow currency search by currency name --- src/pages/iou/IOUCurrencySelection.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/iou/IOUCurrencySelection.js b/src/pages/iou/IOUCurrencySelection.js index 1238d8934f75..05d90975ac7e 100644 --- a/src/pages/iou/IOUCurrencySelection.js +++ b/src/pages/iou/IOUCurrencySelection.js @@ -96,6 +96,7 @@ function IOUCurrencySelection(props) { const currencyOptions = _.map(currencyList, (currencyInfo, currencyCode) => { const isSelectedCurrency = currencyCode === selectedCurrencyCode; return { + currencyName: currencyInfo.name, text: `${currencyCode} - ${CurrencyUtils.getLocalizedCurrencySymbol(currencyCode)}`, currencyCode, keyForList: currencyCode, @@ -105,7 +106,7 @@ function IOUCurrencySelection(props) { }); const searchRegex = new RegExp(Str.escapeForRegExp(searchValue.trim()), 'i'); - const filteredCurrencies = _.filter(currencyOptions, (currencyOption) => searchRegex.test(currencyOption.text)); + const filteredCurrencies = _.filter(currencyOptions, (currencyOption) => searchRegex.test(currencyOption.text) || searchRegex.test(currencyOption.currencyName)); const isEmpty = searchValue.trim() && !filteredCurrencies.length; return { From c1e89eeaacbfdbd208bfd350812de5fda26c81fe Mon Sep 17 00:00:00 2001 From: jeet-dhandha Date: Thu, 21 Sep 2023 18:07:22 +0530 Subject: [PATCH 144/151] fixed styling for scroll view in NewTaskPage --- src/pages/tasks/NewTaskPage.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pages/tasks/NewTaskPage.js b/src/pages/tasks/NewTaskPage.js index 1696961764ca..db63e01c4ce6 100644 --- a/src/pages/tasks/NewTaskPage.js +++ b/src/pages/tasks/NewTaskPage.js @@ -149,8 +149,8 @@ function NewTaskPage(props) { Navigation.goBack(ROUTES.NEW_TASK_DETAILS); }} /> - - + + + + Date: Fri, 22 Sep 2023 08:47:26 +0800 Subject: [PATCH 145/151] Use hooks instead of HOCs in ReportScreen --- src/pages/home/ReportScreen.js | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 63e60a545de9..d5505a582c1c 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -16,15 +16,14 @@ import * as ReportUtils from '../../libs/ReportUtils'; import ReportActionsView from './report/ReportActionsView'; import ReportActionsSkeletonView from '../../components/ReportActionsSkeletonView'; import reportActionPropTypes from './report/reportActionPropTypes'; -import {withNetwork} from '../../components/OnyxProvider'; +import useNetwork from '../../hooks/useNetwork'; +import useWindowDimensions from '../../hooks/useWindowDimensions'; +import useLocalize from '../../hooks/useLocalize'; import compose from '../../libs/compose'; import Visibility from '../../libs/Visibility'; -import networkPropTypes from '../../components/networkPropTypes'; -import withWindowDimensions, {windowDimensionsPropTypes} from '../../components/withWindowDimensions'; import OfflineWithFeedback from '../../components/OfflineWithFeedback'; import ReportFooter from './report/ReportFooter'; import Banner from '../../components/Banner'; -import withLocalize from '../../components/withLocalize'; import reportPropTypes from '../reportPropTypes'; import FullPageNotFoundView from '../../components/BlockingViews/FullPageNotFoundView'; import withViewportOffsetTop, {viewportOffsetTopPropTypes} from '../../components/withViewportOffsetTop'; @@ -80,16 +79,12 @@ const propTypes = { }), ), - /** Information about the network */ - network: networkPropTypes.isRequired, - /** The account manager report ID */ accountManagerReportID: PropTypes.string, /** All of the personal details for everyone */ personalDetails: PropTypes.objectOf(personalDetailsPropType), - ...windowDimensionsPropTypes, ...viewportOffsetTopPropTypes, ...withCurrentReportIDPropTypes, }; @@ -138,15 +133,16 @@ function ReportScreen({ accountManagerReportID, personalDetails, policies, - translate, - network, - isSmallScreenWidth, isSidebarLoaded, viewportOffsetTop, isComposerFullSize, errors, currentReportID, }) { + const {translate} = useLocalize(); + const {isOffline} = useNetwork(); + const {isSmallScreenWidth} = useWindowDimensions(); + const firstRenderRef = useRef(true); const flatListRef = useRef(); const reactionListRef = useRef(); @@ -390,7 +386,7 @@ function ReportScreen({ <> )} @@ -420,9 +416,6 @@ ReportScreen.displayName = 'ReportScreen'; export default compose( withViewportOffsetTop, - withLocalize, - withWindowDimensions, - withNetwork(), withCurrentReportID, withOnyx({ isSidebarLoaded: { From d3533504abb4de692417c34438a035875cd15940 Mon Sep 17 00:00:00 2001 From: rory Date: Fri, 22 Sep 2023 09:30:08 +0800 Subject: [PATCH 146/151] Add testID to all ScreenWrapper components in src/pages --- src/components/ScreenWrapper/index.js | 1 + src/components/ScreenWrapper/propTypes.js | 3 +++ src/pages/AddPersonalBankAccountPage.js | 1 + src/pages/DetailsPage.js | 2 +- src/pages/EditRequestAmountPage.js | 1 + src/pages/EditRequestCategoryPage.js | 1 + src/pages/EditRequestCreatedPage.js | 1 + src/pages/EditRequestDescriptionPage.js | 1 + src/pages/EditRequestMerchantPage.js | 1 + src/pages/EditRequestReceiptPage.js | 1 + src/pages/EnablePayments/AdditionalDetailsStep.js | 1 + src/pages/EnablePayments/EnablePaymentsPage.js | 5 ++++- src/pages/ErrorPage/NotFoundPage.js | 2 +- src/pages/FlagCommentPage.js | 5 ++++- src/pages/GetAssistancePage.js | 2 +- src/pages/NewChatPage.js | 1 + src/pages/NewChatSelectorPage.js | 1 + src/pages/PrivateNotes/PrivateNotesEditPage.js | 1 + src/pages/PrivateNotes/PrivateNotesListPage.js | 5 ++++- src/pages/PrivateNotes/PrivateNotesViewPage.js | 5 ++++- src/pages/ProfilePage.js | 2 +- src/pages/ReimbursementAccount/ACHContractStep.js | 5 ++++- src/pages/ReimbursementAccount/BankAccountManualStep.js | 5 ++++- src/pages/ReimbursementAccount/BankAccountPlaidStep.js | 1 + src/pages/ReimbursementAccount/BankAccountStep.js | 5 ++++- src/pages/ReimbursementAccount/CompanyStep.js | 5 ++++- src/pages/ReimbursementAccount/ContinueBankAccountSetup.js | 5 ++++- src/pages/ReimbursementAccount/EnableStep.js | 1 + src/pages/ReimbursementAccount/ReimbursementAccountPage.js | 4 ++-- src/pages/ReimbursementAccount/RequestorOnfidoStep.js | 1 + src/pages/ReimbursementAccount/RequestorStep.js | 5 ++++- src/pages/ReimbursementAccount/ValidationStep.js | 1 + src/pages/ReportDetailsPage.js | 2 +- src/pages/ReportParticipantsPage.js | 5 ++++- src/pages/ReportWelcomeMessagePage.js | 1 + src/pages/SearchPage.js | 5 ++++- src/pages/ShareCodePage.js | 2 +- src/pages/TeachersUnite/ImTeacherPage.js | 2 +- src/pages/TeachersUnite/IntroSchoolPrincipalPage.js | 5 ++++- src/pages/TeachersUnite/KnowATeacherPage.js | 5 ++++- src/pages/home/ReportScreen.js | 1 + src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js | 1 + src/pages/iou/IOUCurrencySelection.js | 1 + src/pages/iou/MoneyRequestCategoryPage.js | 1 + src/pages/iou/MoneyRequestDatePage.js | 1 + src/pages/iou/MoneyRequestDescriptionPage.js | 1 + src/pages/iou/MoneyRequestMerchantPage.js | 1 + src/pages/iou/MoneyRequestSelectorPage.js | 1 + src/pages/iou/MoneyRequestTagPage.js | 1 + src/pages/iou/SplitBillDetailsPage.js | 2 +- src/pages/iou/WaypointEditor.js | 1 + src/pages/iou/steps/MoneyRequestConfirmPage.js | 5 ++++- .../MoneyRequestParticipantsPage.js | 1 + src/pages/iou/steps/NewRequestAmountPage.js | 1 + src/pages/settings/AboutPage/AboutPage.js | 5 ++++- src/pages/settings/AppDownloadLinks.js | 2 +- src/pages/settings/Preferences/LanguagePage.js | 5 ++++- src/pages/settings/Preferences/PriorityModePage.js | 5 ++++- src/pages/settings/Preferences/ThemePage.js | 5 ++++- .../settings/Profile/Contacts/ContactMethodDetailsPage.js | 7 +++++-- src/pages/settings/Profile/Contacts/ContactMethodsPage.js | 5 ++++- .../settings/Profile/Contacts/NewContactMethodPage.js | 1 + src/pages/settings/Profile/CustomStatus/StatusSetPage.js | 2 +- src/pages/settings/Profile/DisplayNamePage.js | 1 + src/pages/settings/Profile/PersonalDetails/AddressPage.js | 5 ++++- .../settings/Profile/PersonalDetails/DateOfBirthPage.js | 5 ++++- .../settings/Profile/PersonalDetails/LegalNamePage.js | 1 + .../Profile/PersonalDetails/PersonalDetailsInitialPage.js | 2 +- src/pages/settings/Profile/ProfilePage.js | 5 ++++- src/pages/settings/Profile/PronounsPage.js | 5 ++++- src/pages/settings/Profile/TimezoneInitialPage.js | 2 +- src/pages/settings/Profile/TimezoneSelectPage.js | 5 ++++- src/pages/settings/Report/NotificationPreferencePage.js | 5 ++++- src/pages/settings/Report/ReportSettingsPage.js | 2 +- src/pages/settings/Report/RoomNamePage.js | 1 + src/pages/settings/Report/WriteCapabilityPage.js | 5 ++++- src/pages/settings/Security/CloseAccountPage.js | 5 ++++- .../Security/TwoFactorAuth/StepWrapper/StepWrapper.js | 1 + src/pages/settings/Wallet/AddDebitCardPage.js | 1 + src/pages/settings/Wallet/ChooseTransferAccountPage.js | 2 +- src/pages/settings/Wallet/TransferBalancePage.js | 4 ++-- src/pages/settings/Wallet/WalletPage/BaseWalletPage.js | 2 +- src/pages/signin/SignInModal.js | 1 + src/pages/tasks/NewTaskDescriptionPage.js | 1 + src/pages/tasks/NewTaskDetailsPage.js | 1 + src/pages/tasks/NewTaskPage.js | 5 ++++- src/pages/tasks/NewTaskTitlePage.js | 1 + src/pages/tasks/TaskAssigneeSelectorModal.js | 1 + src/pages/tasks/TaskDescriptionPage.js | 1 + src/pages/tasks/TaskShareDestinationSelectorModal.js | 1 + src/pages/tasks/TaskTitlePage.js | 1 + src/pages/wallet/WalletStatementPage.js | 1 + src/pages/workspace/WorkspaceInitialPage.js | 5 ++++- src/pages/workspace/WorkspaceInviteMessagePage.js | 5 ++++- src/pages/workspace/WorkspaceInvitePage.js | 5 ++++- src/pages/workspace/WorkspaceMembersPage.js | 1 + src/pages/workspace/WorkspaceNewRoomPage.js | 1 + src/pages/workspace/WorkspacePageWithSections.js | 1 + 98 files changed, 202 insertions(+), 53 deletions(-) diff --git a/src/components/ScreenWrapper/index.js b/src/components/ScreenWrapper/index.js index f760e5d5aeb4..1cad1e96b26d 100644 --- a/src/components/ScreenWrapper/index.js +++ b/src/components/ScreenWrapper/index.js @@ -109,6 +109,7 @@ class ScreenWrapper extends React.Component { style={styles.flex1} // eslint-disable-next-line react/jsx-props-no-spreading {...(this.props.environment === CONST.ENVIRONMENT.DEV ? this.panResponder.panHandlers : {})} + testID={this.props.testID} > +
    descriptionInputRef.current && descriptionInputRef.current.focus()} + testID="EditRequestDescriptionPage" > merchantInputRef.current && merchantInputRef.current.focus()} + testID="EditRequestMerchantPage" > + {() => { if (this.props.userWallet.errorCode === CONST.WALLET.ERROR.KYC) { return ( diff --git a/src/pages/ErrorPage/NotFoundPage.js b/src/pages/ErrorPage/NotFoundPage.js index f0121cd8f3ef..bcf0da3ae9a2 100644 --- a/src/pages/ErrorPage/NotFoundPage.js +++ b/src/pages/ErrorPage/NotFoundPage.js @@ -5,7 +5,7 @@ import FullPageNotFoundView from '../../components/BlockingViews/FullPageNotFoun // eslint-disable-next-line rulesdir/no-negated-variables function NotFoundPage() { return ( - + ); diff --git a/src/pages/FlagCommentPage.js b/src/pages/FlagCommentPage.js index 1a9a2d8c8767..6af38c84b582 100644 --- a/src/pages/FlagCommentPage.js +++ b/src/pages/FlagCommentPage.js @@ -154,7 +154,10 @@ function FlagCommentPage(props) { )); return ( - + {({safeAreaPaddingBottomStyle}) => ( diff --git a/src/pages/GetAssistancePage.js b/src/pages/GetAssistancePage.js index 34f996936654..d5b790a3b5ca 100644 --- a/src/pages/GetAssistancePage.js +++ b/src/pages/GetAssistancePage.js @@ -78,7 +78,7 @@ function GetAssistancePage(props) { } return ( - + Navigation.goBack(ROUTES.SETTINGS_WORKSPACES)} diff --git a/src/pages/NewChatPage.js b/src/pages/NewChatPage.js index cb54aa8e5a7b..a90ae3030ba2 100755 --- a/src/pages/NewChatPage.js +++ b/src/pages/NewChatPage.js @@ -173,6 +173,7 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate}) includeSafeAreaPaddingBottom={false} includePaddingTop={false} shouldEnableMaxHeight + testID="NewChatPage" > {({safeAreaPaddingBottomStyle, insets}) => ( focusAndUpdateMultilineInputRange(privateNotesInput.current)} + testID="PrivateNotesEditPage" > + + + Navigation.goBack(navigateBackTo)} diff --git a/src/pages/ReimbursementAccount/ACHContractStep.js b/src/pages/ReimbursementAccount/ACHContractStep.js index 1ef43843571c..38604f0bf2b9 100644 --- a/src/pages/ReimbursementAccount/ACHContractStep.js +++ b/src/pages/ReimbursementAccount/ACHContractStep.js @@ -145,7 +145,10 @@ function ACHContractStep(props) { }; return ( - + + + + + + Navigation.goBack(ROUTES.SETTINGS_WORKSPACES)} @@ -385,7 +385,7 @@ class ReimbursementAccountPage extends React.Component { if (errorText) { return ( - + + + diff --git a/src/pages/ReportParticipantsPage.js b/src/pages/ReportParticipantsPage.js index 6ccb7a0c2e87..fe68a5d7ed2b 100755 --- a/src/pages/ReportParticipantsPage.js +++ b/src/pages/ReportParticipantsPage.js @@ -99,7 +99,10 @@ function ReportParticipantsPage(props) { })); return ( - + {({safeAreaPaddingBottomStyle}) => ( {({didScreenTransitionEnd}) => ( diff --git a/src/pages/SearchPage.js b/src/pages/SearchPage.js index 2ee29380ff80..ce43b757428a 100755 --- a/src/pages/SearchPage.js +++ b/src/pages/SearchPage.js @@ -169,7 +169,10 @@ class SearchPage extends Component { ); return ( - + {({didScreenTransitionEnd, safeAreaPaddingBottomStyle}) => ( <> diff --git a/src/pages/ShareCodePage.js b/src/pages/ShareCodePage.js index a36149a5f4fa..6f35bf1b3600 100644 --- a/src/pages/ShareCodePage.js +++ b/src/pages/ShareCodePage.js @@ -54,7 +54,7 @@ class ShareCodePage extends React.Component { const isNative = platform === CONST.PLATFORM.IOS || platform === CONST.PLATFORM.ANDROID; return ( - + Navigation.goBack(isReport ? ROUTES.getReportDetailsRoute(this.props.report.reportID) : ROUTES.SETTINGS)} diff --git a/src/pages/TeachersUnite/ImTeacherPage.js b/src/pages/TeachersUnite/ImTeacherPage.js index dbeba700d208..d4e69dd5dd3b 100644 --- a/src/pages/TeachersUnite/ImTeacherPage.js +++ b/src/pages/TeachersUnite/ImTeacherPage.js @@ -19,7 +19,7 @@ function ImTeacherPage() { const {translate} = useLocalize(); return ( - + Navigation.goBack(ROUTES.TEACHERS_UNITE)} diff --git a/src/pages/TeachersUnite/IntroSchoolPrincipalPage.js b/src/pages/TeachersUnite/IntroSchoolPrincipalPage.js index fe25c54cb8cc..10bc47c00556 100644 --- a/src/pages/TeachersUnite/IntroSchoolPrincipalPage.js +++ b/src/pages/TeachersUnite/IntroSchoolPrincipalPage.js @@ -80,7 +80,10 @@ function IntroSchoolPrincipalPage(props) { ); return ( - + Navigation.goBack(ROUTES.TEACHERS_UNITE)} diff --git a/src/pages/TeachersUnite/KnowATeacherPage.js b/src/pages/TeachersUnite/KnowATeacherPage.js index 795bc170a83c..774217899ce4 100644 --- a/src/pages/TeachersUnite/KnowATeacherPage.js +++ b/src/pages/TeachersUnite/KnowATeacherPage.js @@ -91,7 +91,10 @@ function KnowATeacherPage(props) { ); return ( - + Navigation.goBack(ROUTES.TEACHERS_UNITE)} diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index d5505a582c1c..535949ab522d 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -318,6 +318,7 @@ function ReportScreen({ {({insets}) => ( <> diff --git a/src/pages/iou/IOUCurrencySelection.js b/src/pages/iou/IOUCurrencySelection.js index 1238d8934f75..7854960f8b78 100644 --- a/src/pages/iou/IOUCurrencySelection.js +++ b/src/pages/iou/IOUCurrencySelection.js @@ -131,6 +131,7 @@ function IOUCurrencySelection(props) { optionsSelectorRef.current && optionsSelectorRef.current.focus()} + testID="IOUCurrencySelection" > {({safeAreaPaddingBottomStyle}) => ( <> diff --git a/src/pages/iou/MoneyRequestCategoryPage.js b/src/pages/iou/MoneyRequestCategoryPage.js index 92934a505a04..63eb35d3d4a1 100644 --- a/src/pages/iou/MoneyRequestCategoryPage.js +++ b/src/pages/iou/MoneyRequestCategoryPage.js @@ -63,6 +63,7 @@ function MoneyRequestCategoryPage({route, report, iou}) { focusAndUpdateMultilineInputRange(inputRef.current)} + testID="MoneyRequestDescriptionPage" > inputRef.current && inputRef.current.focus()} + testID="MoneyRequestMerchantPage" > {({safeAreaPaddingBottomStyle}) => ( diff --git a/src/pages/iou/MoneyRequestTagPage.js b/src/pages/iou/MoneyRequestTagPage.js index 60be6035431c..b00643f65f4d 100644 --- a/src/pages/iou/MoneyRequestTagPage.js +++ b/src/pages/iou/MoneyRequestTagPage.js @@ -80,6 +80,7 @@ function MoneyRequestTagPage({route, report, policyTags, iou}) { + textInput.current && textInput.current.focus()} shouldEnableMaxHeight + testID="WaypointEditor" > waypointCount) && isFocused}> + {({safeAreaPaddingBottomStyle}) => ( optionsSelectorRef.current && optionsSelectorRef.current.focus()} + testID="MoneyRequestParticipantsPage" > {({safeAreaPaddingBottomStyle}) => ( diff --git a/src/pages/iou/steps/NewRequestAmountPage.js b/src/pages/iou/steps/NewRequestAmountPage.js index 6e44d8d57e32..c3b581cec642 100644 --- a/src/pages/iou/steps/NewRequestAmountPage.js +++ b/src/pages/iou/steps/NewRequestAmountPage.js @@ -176,6 +176,7 @@ function NewRequestAmountPage({route, iou, report, selectedTab}) { includeSafeAreaPaddingBottom={false} shouldEnableKeyboardAvoidingView={false} onEntryTransitionEnd={focusTextInput} + testID="NewRequestAmountPage" > {({safeAreaPaddingBottomStyle}) => ( diff --git a/src/pages/settings/AboutPage/AboutPage.js b/src/pages/settings/AboutPage/AboutPage.js index 4af48b896fe0..105ea79637bb 100644 --- a/src/pages/settings/AboutPage/AboutPage.js +++ b/src/pages/settings/AboutPage/AboutPage.js @@ -81,7 +81,10 @@ function AboutPage(props) { ]; return ( - + {({safeAreaPaddingBottomStyle}) => ( <> + Navigation.goBack(ROUTES.SETTINGS_ABOUT)} diff --git a/src/pages/settings/Preferences/LanguagePage.js b/src/pages/settings/Preferences/LanguagePage.js index 706fa3b62db6..5004b85312f3 100644 --- a/src/pages/settings/Preferences/LanguagePage.js +++ b/src/pages/settings/Preferences/LanguagePage.js @@ -26,7 +26,10 @@ function LanguagePage(props) { })); return ( - + Navigation.goBack(ROUTES.SETTINGS_PREFERENCES)} diff --git a/src/pages/settings/Preferences/PriorityModePage.js b/src/pages/settings/Preferences/PriorityModePage.js index 0c3d28fe9d81..76791640f9bf 100644 --- a/src/pages/settings/Preferences/PriorityModePage.js +++ b/src/pages/settings/Preferences/PriorityModePage.js @@ -46,7 +46,10 @@ function PriorityModePage(props) { ); return ( - + Navigation.goBack(ROUTES.SETTINGS_PREFERENCES)} diff --git a/src/pages/settings/Preferences/ThemePage.js b/src/pages/settings/Preferences/ThemePage.js index 4e41d5fc7129..a901ee8d350a 100644 --- a/src/pages/settings/Preferences/ThemePage.js +++ b/src/pages/settings/Preferences/ThemePage.js @@ -35,7 +35,10 @@ function ThemePage(props) { })); return ( - + + this.validateCodeFormRef.current && this.validateCodeFormRef.current.focus()}> + this.validateCodeFormRef.current && this.validateCodeFormRef.current.focus()} + testID="ContactMethodDetailsPage" + > Navigation.goBack(ROUTES.SETTINGS_CONTACT_METHODS)} diff --git a/src/pages/settings/Profile/Contacts/ContactMethodsPage.js b/src/pages/settings/Profile/Contacts/ContactMethodsPage.js index 62e62183a156..41b44e5b6339 100644 --- a/src/pages/settings/Profile/Contacts/ContactMethodsPage.js +++ b/src/pages/settings/Profile/Contacts/ContactMethodsPage.js @@ -111,7 +111,10 @@ function ContactMethodsPage(props) { }); return ( - + Navigation.goBack(ROUTES.SETTINGS_PROFILE)} diff --git a/src/pages/settings/Profile/Contacts/NewContactMethodPage.js b/src/pages/settings/Profile/Contacts/NewContactMethodPage.js index 236b7c1c73aa..0de9c34e14b6 100644 --- a/src/pages/settings/Profile/Contacts/NewContactMethodPage.js +++ b/src/pages/settings/Profile/Contacts/NewContactMethodPage.js @@ -98,6 +98,7 @@ function NewContactMethodPage(props) { }} includeSafeAreaPaddingBottom={false} shouldEnableMaxHeight + testID="NewContactMethodPage" > + Navigation.goBack(ROUTES.SETTINGS_STATUS)} diff --git a/src/pages/settings/Profile/DisplayNamePage.js b/src/pages/settings/Profile/DisplayNamePage.js index d93eeed2689c..d4924636acbc 100644 --- a/src/pages/settings/Profile/DisplayNamePage.js +++ b/src/pages/settings/Profile/DisplayNamePage.js @@ -69,6 +69,7 @@ function DisplayNamePage(props) { + + Navigation.goBack(ROUTES.SETTINGS_PERSONAL_DETAILS)} diff --git a/src/pages/settings/Profile/PersonalDetails/LegalNamePage.js b/src/pages/settings/Profile/PersonalDetails/LegalNamePage.js index 0caf20a3e128..4fb538ac59b6 100644 --- a/src/pages/settings/Profile/PersonalDetails/LegalNamePage.js +++ b/src/pages/settings/Profile/PersonalDetails/LegalNamePage.js @@ -71,6 +71,7 @@ function LegalNamePage(props) { + Navigation.goBack(ROUTES.SETTINGS_PROFILE)} diff --git a/src/pages/settings/Profile/ProfilePage.js b/src/pages/settings/Profile/ProfilePage.js index 1864ba60524c..edc6e29b58a6 100755 --- a/src/pages/settings/Profile/ProfilePage.js +++ b/src/pages/settings/Profile/ProfilePage.js @@ -107,7 +107,10 @@ function ProfilePage(props) { }, [props.currentUserPersonalDetails]); return ( - + Navigation.goBack(ROUTES.SETTINGS)} diff --git a/src/pages/settings/Profile/PronounsPage.js b/src/pages/settings/Profile/PronounsPage.js index 5f8824046d69..4f8fb7cdb0df 100644 --- a/src/pages/settings/Profile/PronounsPage.js +++ b/src/pages/settings/Profile/PronounsPage.js @@ -65,7 +65,10 @@ function PronounsPage({currentUserPersonalDetails}) { }; return ( - + Navigation.goBack(ROUTES.SETTINGS_PROFILE)} diff --git a/src/pages/settings/Profile/TimezoneInitialPage.js b/src/pages/settings/Profile/TimezoneInitialPage.js index 94cb40a3f6f3..101d5b2cf77b 100644 --- a/src/pages/settings/Profile/TimezoneInitialPage.js +++ b/src/pages/settings/Profile/TimezoneInitialPage.js @@ -41,7 +41,7 @@ function TimezoneInitialPage(props) { }; return ( - + Navigation.goBack(ROUTES.SETTINGS_PROFILE)} diff --git a/src/pages/settings/Profile/TimezoneSelectPage.js b/src/pages/settings/Profile/TimezoneSelectPage.js index 228bae5cbb6b..05de3657e0ac 100644 --- a/src/pages/settings/Profile/TimezoneSelectPage.js +++ b/src/pages/settings/Profile/TimezoneSelectPage.js @@ -78,7 +78,10 @@ function TimezoneSelectPage(props) { }; return ( - + Navigation.goBack(ROUTES.SETTINGS_TIMEZONE)} diff --git a/src/pages/settings/Report/NotificationPreferencePage.js b/src/pages/settings/Report/NotificationPreferencePage.js index 17bd77a235ce..5fdb07ceddfa 100644 --- a/src/pages/settings/Report/NotificationPreferencePage.js +++ b/src/pages/settings/Report/NotificationPreferencePage.js @@ -43,7 +43,10 @@ function NotificationPreferencePage(props) { ); return ( - + + roomNameInputRef.current && roomNameInputRef.current.focus()} includeSafeAreaPaddingBottom={false} + testID="RoomNamePage" > + + Navigation.goBack(ROUTES.SETTINGS_SECURITY)} diff --git a/src/pages/settings/Security/TwoFactorAuth/StepWrapper/StepWrapper.js b/src/pages/settings/Security/TwoFactorAuth/StepWrapper/StepWrapper.js index 16f9489d87b0..817bd9e5fb5b 100644 --- a/src/pages/settings/Security/TwoFactorAuth/StepWrapper/StepWrapper.js +++ b/src/pages/settings/Security/TwoFactorAuth/StepWrapper/StepWrapper.js @@ -24,6 +24,7 @@ function StepWrapper({ nameOnCardRef.current && nameOnCardRef.current.focus()} includeSafeAreaPaddingBottom={false} + testID="AddDebitCardPage" > + Navigation.goBack(ROUTES.SETTINGS_WALLET_TRANSFER_BALANCE)} diff --git a/src/pages/settings/Wallet/TransferBalancePage.js b/src/pages/settings/Wallet/TransferBalancePage.js index b52695c2e922..10e591e5945b 100644 --- a/src/pages/settings/Wallet/TransferBalancePage.js +++ b/src/pages/settings/Wallet/TransferBalancePage.js @@ -137,7 +137,7 @@ function TransferBalancePage(props) { if (props.walletTransfer.shouldShowSuccess && !props.walletTransfer.loading) { return ( - + + + Navigation.goBack(ROUTES.SETTINGS)} diff --git a/src/pages/signin/SignInModal.js b/src/pages/signin/SignInModal.js index 50b0af0bfa05..03788298debf 100644 --- a/src/pages/signin/SignInModal.js +++ b/src/pages/signin/SignInModal.js @@ -22,6 +22,7 @@ function SignInModal() { style={[styles.highlightBG]} includeSafeAreaPaddingBottom={false} shouldEnableMaxHeight + testID="SignInModal" > diff --git a/src/pages/tasks/NewTaskDescriptionPage.js b/src/pages/tasks/NewTaskDescriptionPage.js index dacf368a574e..008e31a96c1d 100644 --- a/src/pages/tasks/NewTaskDescriptionPage.js +++ b/src/pages/tasks/NewTaskDescriptionPage.js @@ -57,6 +57,7 @@ function NewTaskDescriptionPage(props) { includeSafeAreaPaddingBottom={false} onEntryTransitionEnd={() => focusAndUpdateMultilineInputRange(inputRef.current)} shouldEnableMaxHeight + testID="NewTaskDescriptionPage" > inputRef.current && inputRef.current.focus()} includeSafeAreaPaddingBottom={false} shouldEnableMaxHeight + testID="NewTaskDetailsPage" > + Task.dismissModalAndClearOutTaskInfo()} diff --git a/src/pages/tasks/NewTaskTitlePage.js b/src/pages/tasks/NewTaskTitlePage.js index 14d7e7d23b9e..8b7401cc3023 100644 --- a/src/pages/tasks/NewTaskTitlePage.js +++ b/src/pages/tasks/NewTaskTitlePage.js @@ -77,6 +77,7 @@ function NewTaskTitlePage(props) { }} includeSafeAreaPaddingBottom={false} shouldEnableMaxHeight + testID="NewTaskTitlePage" > optionRef.current && optionRef.current.textInput.focus()} + testID="TaskAssigneeeSelectorModal" > {({didScreenTransitionEnd, safeAreaPaddingBottomStyle}) => ( diff --git a/src/pages/tasks/TaskDescriptionPage.js b/src/pages/tasks/TaskDescriptionPage.js index 382e8d80d4ad..3a336486dbfd 100644 --- a/src/pages/tasks/TaskDescriptionPage.js +++ b/src/pages/tasks/TaskDescriptionPage.js @@ -67,6 +67,7 @@ function TaskDescriptionPage(props) { includeSafeAreaPaddingBottom={false} onEntryTransitionEnd={() => focusAndUpdateMultilineInputRange(inputRef.current)} shouldEnableMaxHeight + testID="TaskDescriptionPage" > {({didScreenTransitionEnd}) => ( diff --git a/src/pages/tasks/TaskShareDestinationSelectorModal.js b/src/pages/tasks/TaskShareDestinationSelectorModal.js index 44d7b1f6a633..45694492aaca 100644 --- a/src/pages/tasks/TaskShareDestinationSelectorModal.js +++ b/src/pages/tasks/TaskShareDestinationSelectorModal.js @@ -116,6 +116,7 @@ function TaskShareDestinationSelectorModal(props) { optionRef.current && optionRef.current.textInput.focus()} + testID="TaskShareDestinationSelectorModal" > {({didScreenTransitionEnd, safeAreaPaddingBottomStyle}) => ( <> diff --git a/src/pages/tasks/TaskTitlePage.js b/src/pages/tasks/TaskTitlePage.js index be0884e971ae..71da6462b118 100644 --- a/src/pages/tasks/TaskTitlePage.js +++ b/src/pages/tasks/TaskTitlePage.js @@ -79,6 +79,7 @@ function TaskTitlePage(props) { includeSafeAreaPaddingBottom={false} onEntryTransitionEnd={() => inputRef.current && inputRef.current.focus()} shouldEnableMaxHeight + testID="TaskTitlePage" > {({didScreenTransitionEnd}) => ( diff --git a/src/pages/wallet/WalletStatementPage.js b/src/pages/wallet/WalletStatementPage.js index cbd9b72e3299..336f2cdda0aa 100644 --- a/src/pages/wallet/WalletStatementPage.js +++ b/src/pages/wallet/WalletStatementPage.js @@ -94,6 +94,7 @@ function WalletStatementPage(props) { + {({safeAreaPaddingBottomStyle}) => ( Navigation.goBack(ROUTES.SETTINGS_WORKSPACES)} diff --git a/src/pages/workspace/WorkspaceInviteMessagePage.js b/src/pages/workspace/WorkspaceInviteMessagePage.js index 3baf84f54ccf..b72e71f18cc5 100644 --- a/src/pages/workspace/WorkspaceInviteMessagePage.js +++ b/src/pages/workspace/WorkspaceInviteMessagePage.js @@ -158,7 +158,10 @@ class WorkspaceInviteMessagePage extends React.Component { const policyName = lodashGet(this.props.policy, 'name'); return ( - + + {({didScreenTransitionEnd}) => { const sections = didScreenTransitionEnd ? getSections() : []; diff --git a/src/pages/workspace/WorkspaceMembersPage.js b/src/pages/workspace/WorkspaceMembersPage.js index 7bb9a91c130e..669b0e41b08d 100644 --- a/src/pages/workspace/WorkspaceMembersPage.js +++ b/src/pages/workspace/WorkspaceMembersPage.js @@ -344,6 +344,7 @@ function WorkspaceMembersPage(props) { {({insets}) => ( Navigation.goBack(ROUTES.SETTINGS_WORKSPACES)} From 83aa25962d629a0cf62c928e4e327efd0af87b0b Mon Sep 17 00:00:00 2001 From: rory Date: Fri, 22 Sep 2023 09:40:13 +0800 Subject: [PATCH 147/151] Add testID to all ScreenWrapper components in src/components --- src/components/AvatarCropModal/AvatarCropModal.js | 1 + .../CountryPicker/CountrySelectorModal.js | 1 + src/components/DistanceRequest.js | 1 + src/components/HeaderPageLayout.js | 1 + .../NewDatePicker/CalendarPicker/YearPickerModal.js | 1 + .../ReimbursementAccountLoadingIndicator.js | 13 ++++++------- src/components/StatePicker/StateSelectorModal.js | 1 + 7 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/components/AvatarCropModal/AvatarCropModal.js b/src/components/AvatarCropModal/AvatarCropModal.js index baa958106f84..d16ba1364b71 100644 --- a/src/components/AvatarCropModal/AvatarCropModal.js +++ b/src/components/AvatarCropModal/AvatarCropModal.js @@ -366,6 +366,7 @@ function AvatarCropModal(props) { style={[styles.pb0]} includePaddingTop={false} includeSafeAreaPaddingBottom={false} + testID="AvatarCropModal" > {props.isSmallScreenWidth && } {({safeAreaPaddingBottomStyle}) => ( diff --git a/src/components/HeaderPageLayout.js b/src/components/HeaderPageLayout.js index bec1e52b1cad..b57a5bc85e82 100644 --- a/src/components/HeaderPageLayout.js +++ b/src/components/HeaderPageLayout.js @@ -58,6 +58,7 @@ function HeaderPageLayout({backgroundColor, children, footer, headerContainerSty shouldEnablePickerAvoiding={false} includeSafeAreaPaddingBottom={false} offlineIndicatorStyle={[appBGColor]} + testID="HeaderPageLayout" > {({safeAreaPaddingBottomStyle}) => ( <> diff --git a/src/components/NewDatePicker/CalendarPicker/YearPickerModal.js b/src/components/NewDatePicker/CalendarPicker/YearPickerModal.js index 1149f9dc56ce..d0d52daa9206 100644 --- a/src/components/NewDatePicker/CalendarPicker/YearPickerModal.js +++ b/src/components/NewDatePicker/CalendarPicker/YearPickerModal.js @@ -64,6 +64,7 @@ function YearPickerModal(props) { style={[styles.pb0]} includePaddingTop={false} includeSafeAreaPaddingBottom={false} + testID="YearPickerModal" > @@ -42,7 +41,7 @@ function ReimbursementAccountLoadingIndicator(props) { style={styles.loadingVBAAnimation} /> - {props.translate('reimbursementAccountLoadingAnimation.explanationLine')} + {translate('reimbursementAccountLoadingAnimation.explanationLine')} ) : ( @@ -56,4 +55,4 @@ function ReimbursementAccountLoadingIndicator(props) { ReimbursementAccountLoadingIndicator.propTypes = propTypes; ReimbursementAccountLoadingIndicator.displayName = 'ReimbursementAccountLoadingIndicator'; -export default compose(withLocalize, withNetwork())(ReimbursementAccountLoadingIndicator); +export default ReimbursementAccountLoadingIndicator; diff --git a/src/components/StatePicker/StateSelectorModal.js b/src/components/StatePicker/StateSelectorModal.js index bea335840c53..a5e20470977e 100644 --- a/src/components/StatePicker/StateSelectorModal.js +++ b/src/components/StatePicker/StateSelectorModal.js @@ -84,6 +84,7 @@ function StateSelectorModal({currentState, isVisible, onClose, onStateSelected, style={[styles.pb0]} includePaddingTop={false} includeSafeAreaPaddingBottom={false} + testID="StateSelectorModal" > Date: Fri, 22 Sep 2023 10:27:12 +0800 Subject: [PATCH 148/151] Fix lint --- src/pages/home/ReportScreen.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index f8ea8de640bd..f2b0875dbc53 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -88,7 +88,6 @@ const propTypes = { /** Whether user is leaving the current report */ userLeavingStatus: PropTypes.bool, - ...windowDimensionsPropTypes, ...viewportOffsetTopPropTypes, ...withCurrentReportIDPropTypes, }; From 4cdd4b171606096b728b4ace0567f67f2ffe4a36 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Fri, 22 Sep 2023 08:30:20 +0530 Subject: [PATCH 149/151] add hub for exports --- docs/new-expensify/hubs/exports.html | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/new-expensify/hubs/exports.html b/docs/new-expensify/hubs/exports.html index e69de29bb2d1..16c96cb51d01 100644 --- a/docs/new-expensify/hubs/exports.html +++ b/docs/new-expensify/hubs/exports.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Exports +--- + +{% include hub.html %} \ No newline at end of file From 869aa1c99a966cfb89284b82e76ff801e7289f51 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Fri, 22 Sep 2023 08:50:41 +0530 Subject: [PATCH 150/151] fix title case --- .github/scripts/createDocsRoutes.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/scripts/createDocsRoutes.js b/.github/scripts/createDocsRoutes.js index 39cd98383de1..b371caee24b4 100644 --- a/.github/scripts/createDocsRoutes.js +++ b/.github/scripts/createDocsRoutes.js @@ -16,7 +16,8 @@ const platformNames = { * @returns {String} */ function toTitleCase(str) { - return str.replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()); + console.log(str) + return str.replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.substr(1)); } /** From 4f975528315842e5b0c15b43938721e965f409ee Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Fri, 22 Sep 2023 08:51:28 +0530 Subject: [PATCH 151/151] remove console log --- .github/scripts/createDocsRoutes.js | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/scripts/createDocsRoutes.js b/.github/scripts/createDocsRoutes.js index b371caee24b4..6604a9d207fa 100644 --- a/.github/scripts/createDocsRoutes.js +++ b/.github/scripts/createDocsRoutes.js @@ -16,7 +16,6 @@ const platformNames = { * @returns {String} */ function toTitleCase(str) { - console.log(str) return str.replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.substr(1)); }