From 82a0275f45083737f74288c95d40a4d791c7e218 Mon Sep 17 00:00:00 2001 From: cdOut <88325488+cdOut@users.noreply.github.com> Date: Fri, 24 Nov 2023 17:03:55 +0100 Subject: [PATCH 01/29] add FormProvider in page --- src/pages/workspace/WorkspaceInviteMessagePage.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/pages/workspace/WorkspaceInviteMessagePage.js b/src/pages/workspace/WorkspaceInviteMessagePage.js index 85dd65377dcf..739f7bf3695a 100644 --- a/src/pages/workspace/WorkspaceInviteMessagePage.js +++ b/src/pages/workspace/WorkspaceInviteMessagePage.js @@ -27,6 +27,8 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import {policyDefaultProps, policyPropTypes} from './withPolicy'; import withPolicyAndFullscreenLoading from './withPolicyAndFullscreenLoading'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapper from '@components/Form/InputWrapper'; const personalDetailsPropTypes = PropTypes.shape({ /** The accountID of the person */ @@ -178,7 +180,7 @@ class WorkspaceInviteMessagePage extends React.Component { onBackButtonPress={() => Navigation.goBack(ROUTES.WORKSPACE_INVITE.getRoute(this.props.route.params.policyID))} /> -
{this.props.translate('workspace.inviteMessage.inviteMessagePrompt')} - (this.welcomeMessageInputRef = el)} role={CONST.ACCESSIBILITY_ROLE.TEXT} inputID="welcomeMessage" @@ -233,7 +236,7 @@ class WorkspaceInviteMessagePage extends React.Component { shouldSaveDraft /> -
+ ); From 4f479e2f4116b114f79c1a3c82d1073d1c509c70 Mon Sep 17 00:00:00 2001 From: cdOut <88325488+cdOut@users.noreply.github.com> Date: Fri, 24 Nov 2023 17:04:30 +0100 Subject: [PATCH 02/29] fix prettier --- src/pages/workspace/WorkspaceInviteMessagePage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/workspace/WorkspaceInviteMessagePage.js b/src/pages/workspace/WorkspaceInviteMessagePage.js index 739f7bf3695a..273bcba6e6c2 100644 --- a/src/pages/workspace/WorkspaceInviteMessagePage.js +++ b/src/pages/workspace/WorkspaceInviteMessagePage.js @@ -6,6 +6,8 @@ import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import Form from '@components/Form'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapper from '@components/Form/InputWrapper'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import MultipleAvatars from '@components/MultipleAvatars'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; @@ -27,8 +29,6 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import {policyDefaultProps, policyPropTypes} from './withPolicy'; import withPolicyAndFullscreenLoading from './withPolicyAndFullscreenLoading'; -import FormProvider from '@components/Form/FormProvider'; -import InputWrapper from '@components/Form/InputWrapper'; const personalDetailsPropTypes = PropTypes.shape({ /** The accountID of the person */ From 148798661d3f72556e3134583245ba8d951e6965 Mon Sep 17 00:00:00 2001 From: cdOut <88325488+cdOut@users.noreply.github.com> Date: Fri, 24 Nov 2023 17:05:17 +0100 Subject: [PATCH 03/29] remove unused imports --- src/pages/workspace/WorkspaceInviteMessagePage.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/workspace/WorkspaceInviteMessagePage.js b/src/pages/workspace/WorkspaceInviteMessagePage.js index 273bcba6e6c2..825c130eeaac 100644 --- a/src/pages/workspace/WorkspaceInviteMessagePage.js +++ b/src/pages/workspace/WorkspaceInviteMessagePage.js @@ -5,7 +5,6 @@ import {Keyboard, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; -import Form from '@components/Form'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; From 512de40902e2d00f4e58891c0b7b3045dbd6fe56 Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Fri, 8 Dec 2023 13:08:23 +0100 Subject: [PATCH 04/29] [TS migration] Migrate 'ReportActionItemMessage.js' component --- src/libs/ReportUtils.ts | 6 +- .../home/report/ReportActionItemMessage.js | 101 ------------------ .../home/report/ReportActionItemMessage.tsx | 93 ++++++++++++++++ src/types/onyx/OriginalMessage.ts | 4 +- src/types/onyx/ReportAction.ts | 3 + 5 files changed, 102 insertions(+), 105 deletions(-) delete mode 100644 src/pages/home/report/ReportActionItemMessage.js create mode 100644 src/pages/home/report/ReportActionItemMessage.tsx diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 608c4333ab0f..a6393780d2a7 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1000,16 +1000,16 @@ function isOneOnOneChat(report: OnyxEntry): boolean { /** * Get the report given a reportID */ -function getReport(reportID: string | undefined): OnyxEntry | EmptyObject { +function getReport(reportID: string | undefined): OnyxEntry { /** * Using typical string concatenation here due to performance issues * with template literals. */ if (!allReports) { - return {}; + return null; } - return allReports?.[ONYXKEYS.COLLECTION.REPORT + reportID] ?? {}; + return allReports?.[ONYXKEYS.COLLECTION.REPORT + reportID] ?? null; } /** diff --git a/src/pages/home/report/ReportActionItemMessage.js b/src/pages/home/report/ReportActionItemMessage.js deleted file mode 100644 index 2265530f29a1..000000000000 --- a/src/pages/home/report/ReportActionItemMessage.js +++ /dev/null @@ -1,101 +0,0 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; -import React from 'react'; -import {Text, View} from 'react-native'; -import _ from 'underscore'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; -import * as ReportActionsUtils from '@libs/ReportActionsUtils'; -import * as ReportUtils from '@libs/ReportUtils'; -import useThemeStyles from '@styles/useThemeStyles'; -import CONST from '@src/CONST'; -import ReportActionItemFragment from './ReportActionItemFragment'; -import reportActionPropTypes from './reportActionPropTypes'; - -const propTypes = { - /** The report action */ - action: PropTypes.shape(reportActionPropTypes).isRequired, - - /** Should the comment have the appearance of being grouped with the previous comment? */ - displayAsGroup: PropTypes.bool.isRequired, - - /** Additional styles to add after local styles. */ - style: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]), - - /** Whether or not the message is hidden by moderation */ - isHidden: PropTypes.bool, - - /** The ID of the report */ - reportID: PropTypes.string.isRequired, - - /** localization props */ - ...withLocalizePropTypes, -}; - -const defaultProps = { - style: [], - isHidden: false, -}; - -function ReportActionItemMessage(props) { - const styles = useThemeStyles(); - const fragments = _.compact(props.action.previousMessage || props.action.message); - const isIOUReport = ReportActionsUtils.isMoneyRequestAction(props.action); - let iouMessage; - if (isIOUReport) { - const iouReportID = lodashGet(props.action, 'originalMessage.IOUReportID'); - if (iouReportID) { - iouMessage = ReportUtils.getReportPreviewMessage(ReportUtils.getReport(iouReportID), props.action); - } - } - - const isApprovedOrSubmittedReportAction = _.contains([CONST.REPORT.ACTIONS.TYPE.APPROVED, CONST.REPORT.ACTIONS.TYPE.SUBMITTED], props.action.actionName); - - /** - * Get the ReportActionItemFragments - * @param {Boolean} shouldWrapInText determines whether the fragments are wrapped in a Text component - * @returns {Object} report action item fragments - */ - const renderReportActionItemFragments = (shouldWrapInText) => { - const reportActionItemFragments = _.map(fragments, (fragment, index) => ( - - )); - - // Approving or submitting reports in oldDot results in system messages made up of multiple fragments of `TEXT` type - // which we need to wrap in `` to prevent them rendering on separate lines. - - return shouldWrapInText ? {reportActionItemFragments} : reportActionItemFragments; - }; - - return ( - - {!props.isHidden ? ( - renderReportActionItemFragments(isApprovedOrSubmittedReportAction) - ) : ( - {props.translate('moderation.flaggedContent')} - )} - - ); -} - -ReportActionItemMessage.propTypes = propTypes; -ReportActionItemMessage.defaultProps = defaultProps; -ReportActionItemMessage.displayName = 'ReportActionItemMessage'; - -export default withLocalize(ReportActionItemMessage); diff --git a/src/pages/home/report/ReportActionItemMessage.tsx b/src/pages/home/report/ReportActionItemMessage.tsx new file mode 100644 index 000000000000..4f70e98d44b9 --- /dev/null +++ b/src/pages/home/report/ReportActionItemMessage.tsx @@ -0,0 +1,93 @@ +import React, {ReactElement} from 'react'; +import {StyleProp, Text, View, ViewStyle} from 'react-native'; +import useLocalize from '@hooks/useLocalize'; +import * as ReportActionsUtils from '@libs/ReportActionsUtils'; +import * as ReportUtils from '@libs/ReportUtils'; +import useThemeStyles from '@styles/useThemeStyles'; +import CONST from '@src/CONST'; +import {ReportAction} from '@src/types/onyx'; +import {OriginalMessageAddComment} from '@src/types/onyx/OriginalMessage'; +import ReportActionItemFragment from './ReportActionItemFragment'; + +type ReportActionItemMessageProps = { + /** The report action */ + action: ReportAction; + + /** Should the comment have the appearance of being grouped with the previous comment? */ + displayAsGroup: boolean; + + /** Additional styles to add after local styles. */ + style?: StyleProp; + + /** Whether or not the message is hidden by moderation */ + isHidden?: boolean; + + /** The ID of the report */ + reportID: string; +}; + +function ReportActionItemMessage({action, displayAsGroup, reportID, style, isHidden = false}: ReportActionItemMessageProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + const fragments = (action.previousMessage ?? action.message ?? []).filter((item) => !!item); + const isIOUReport = ReportActionsUtils.isMoneyRequestAction(action); + let iouMessage: string | undefined; + if (isIOUReport) { + const originalMessage = action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? action.originalMessage : null; + const iouReportID = originalMessage?.IOUReportID; + if (iouReportID) { + iouMessage = ReportUtils.getReportPreviewMessage(ReportUtils.getReport(iouReportID), action); + } + } + + const isApprovedOrSubmittedReportAction = [CONST.REPORT.ACTIONS.TYPE.APPROVED, CONST.REPORT.ACTIONS.TYPE.SUBMITTED].some((type) => type === action.actionName); + + /** + * Get the ReportActionItemFragments + * @param shouldWrapInText determines whether the fragments are wrapped in a Text component + * @returns report action item fragments + */ + const renderReportActionItemFragments = (shouldWrapInText: boolean): ReactElement | ReactElement[] => { + const reportActionItemFragments = fragments.map((fragment, index) => ( + + )); + + // Approving or submitting reports in oldDot results in system messages made up of multiple fragments of `TEXT` type + // which we need to wrap in `` to prevent them rendering on separate lines. + + return shouldWrapInText ? {reportActionItemFragments} : reportActionItemFragments; + }; + + return ( + + {!isHidden ? ( + renderReportActionItemFragments(isApprovedOrSubmittedReportAction) + ) : ( + {translate('moderation.flaggedContent')} + )} + + ); +} + +ReportActionItemMessage.displayName = 'ReportActionItemMessage'; + +export default ReportActionItemMessage; diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts index f76fbd5ffd7d..c44589a4a105 100644 --- a/src/types/onyx/OriginalMessage.ts +++ b/src/types/onyx/OriginalMessage.ts @@ -24,6 +24,7 @@ type OriginalMessageApproved = { actionName: typeof CONST.REPORT.ACTIONS.TYPE.APPROVED; originalMessage: unknown; }; +type OriginalMessageSource = 'Chronos' | 'email' | 'ios' | 'android' | 'web' | ''; type IOUDetails = { amount: number; @@ -94,6 +95,7 @@ type OriginalMessageAddComment = { actionName: typeof CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT; originalMessage: { html: string; + source?: OriginalMessageSource; lastModified?: string; taskReportID?: string; edits?: string[]; @@ -231,4 +233,4 @@ type OriginalMessage = | OriginalMessageMoved; export default OriginalMessage; -export type {ChronosOOOEvent, Decision, Reaction, ActionName, IOUMessage, Closed, OriginalMessageActionName, ChangeLog}; +export type {ChronosOOOEvent, Decision, Reaction, ActionName, IOUMessage, Closed, OriginalMessageActionName, ChangeLog, OriginalMessageAddComment}; diff --git a/src/types/onyx/ReportAction.ts b/src/types/onyx/ReportAction.ts index a0e90f4e9c34..ee4345f50071 100644 --- a/src/types/onyx/ReportAction.ts +++ b/src/types/onyx/ReportAction.ts @@ -79,6 +79,9 @@ type ReportActionBase = { /** report action message */ message?: Message[]; + /** previous report action message */ + previousMessage?: Message[]; + /** Whether we have received a response back from the server */ isLoading?: boolean; From e307df9285066974f117ce2bbd028a546c6ae84d Mon Sep 17 00:00:00 2001 From: cdOut <88325488+cdOut@users.noreply.github.com> Date: Fri, 8 Dec 2023 16:13:13 +0100 Subject: [PATCH 05/29] migrate from class based component to functional --- .../workspace/WorkspaceInviteMessagePage.js | 252 +++++++----------- 1 file changed, 98 insertions(+), 154 deletions(-) diff --git a/src/pages/workspace/WorkspaceInviteMessagePage.js b/src/pages/workspace/WorkspaceInviteMessagePage.js index 6d761b1130a8..e8dd1257b01b 100644 --- a/src/pages/workspace/WorkspaceInviteMessagePage.js +++ b/src/pages/workspace/WorkspaceInviteMessagePage.js @@ -18,7 +18,6 @@ import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import withNavigationFocus from '@components/withNavigationFocus'; import withThemeStyles, {withThemeStylesPropTypes} from '@components/withThemeStyles'; import compose from '@libs/compose'; -import * as Localize from '@libs/Localize'; import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; @@ -71,181 +70,126 @@ const defaultProps = { invitedEmailsToAccountIDsDraft: {}, }; -class WorkspaceInviteMessagePage extends React.Component { - constructor(props) { - super(props); - - this.sendInvitation = this.sendInvitation.bind(this); - this.validate = this.validate.bind(this); - this.openPrivacyURL = this.openPrivacyURL.bind(this); - this.debouncedSaveDraf = _.debounce((newDraft) => { - Policy.setWorkspaceInviteMessageDraft(this.props.route.params.policyID, newDraft); - }, 2000); - this.state = { - welcomeNote: this.props.workspaceInviteMessageDraft || this.getDefaultWelcomeNote(), - }; - } - - componentDidMount() { - if (_.isEmpty(this.props.invitedEmailsToAccountIDsDraft)) { - Navigation.goBack(ROUTES.WORKSPACE_INVITE.getRoute(this.props.route.params.policyID), true); - return; - } - this.focusWelcomeMessageInput(); +function WorkspaceInviteMessagePage(props) { + if (_.isEmpty(props.invitedEmailsToAccountIDsDraft)) { + Navigation.goBack(ROUTES.WORKSPACE_INVITE.getRoute(props.route.params.policyID), true); + return; } - componentDidUpdate(prevProps) { - if (!prevProps.isFocused && this.props.isFocused) { - this.focusWelcomeMessageInput(); - } - - if ( - !( - (prevProps.preferredLocale !== this.props.preferredLocale || prevProps.policy.name !== this.props.policy.name) && - this.state.welcomeNote === Localize.translate(prevProps.preferredLocale, 'workspace.inviteMessage.welcomeNote', {workspaceName: prevProps.policy.name}) - ) - ) { - return; - } - this.setState({welcomeNote: this.getDefaultWelcomeNote()}); - } - - componentWillUnmount() { - if (!this.focusTimeout) { - return; - } - clearTimeout(this.focusTimeout); - } + const saveDraft = (newDraft) => { + Policy.setWorkspaceInviteMessageDraft(props.route.params.policyID, newDraft); + }; - getDefaultWelcomeNote() { - return this.props.translate('workspace.inviteMessage.welcomeNote', { - workspaceName: this.props.policy.name, + const getDefaultWelcomeNote = () => + props.translate('workspace.inviteMessage.welcomeNote', { + workspaceName: props.policy.name, }); - } - sendInvitation() { + const welcomeMessage = props.workspaceInviteMessageDraft || getDefaultWelcomeNote(); + + const sendInvitation = () => { Keyboard.dismiss(); - Policy.addMembersToWorkspace(this.props.invitedEmailsToAccountIDsDraft, this.state.welcomeNote, this.props.route.params.policyID); - Policy.setWorkspaceInviteMembersDraft(this.props.route.params.policyID, {}); + Policy.addMembersToWorkspace(props.invitedEmailsToAccountIDsDraft, welcomeMessage, props.route.params.policyID); + Policy.setWorkspaceInviteMembersDraft(props.route.params.policyID, {}); // Pop the invite message page before navigating to the members page. Navigation.goBack(ROUTES.HOME); - Navigation.navigate(ROUTES.WORKSPACE_MEMBERS.getRoute(this.props.route.params.policyID)); - } + Navigation.navigate(ROUTES.WORKSPACE_MEMBERS.getRoute(props.route.params.policyID)); + }; /** * Opens privacy url as an external link * @param {Object} event */ - openPrivacyURL(event) { + const openPrivacyURL = (event) => { event.preventDefault(); Link.openExternalLink(CONST.PRIVACY_URL); - } - - focusWelcomeMessageInput() { - this.focusTimeout = setTimeout(() => { - this.welcomeMessageInputRef.focus(); - // Below condition is needed for web, desktop and mweb only, for native cursor is set at end by default. - if (this.welcomeMessageInputRef.value && this.welcomeMessageInputRef.setSelectionRange) { - const length = this.welcomeMessageInputRef.value.length; - this.welcomeMessageInputRef.setSelectionRange(length, length); - } - }, CONST.ANIMATED_TRANSITION); - } + }; - validate() { + const validate = () => { const errorFields = {}; - if (_.isEmpty(this.props.invitedEmailsToAccountIDsDraft)) { + if (_.isEmpty(props.invitedEmailsToAccountIDsDraft)) { errorFields.welcomeMessage = 'workspace.inviteMessage.inviteNoMembersError'; } return errorFields; - } - - render() { - const policyName = lodashGet(this.props.policy, 'name'); - - return ( - + Navigation.goBack(ROUTES.SETTINGS_WORKSPACES)} > - Navigation.goBack(ROUTES.SETTINGS_WORKSPACES)} + Navigation.dismissModal()} + onBackButtonPress={() => Navigation.goBack(ROUTES.WORKSPACE_INVITE.getRoute(props.route.params.policyID))} + /> + + + + {props.translate('common.privacy')} + + + } > - Navigation.dismissModal()} - onBackButtonPress={() => Navigation.goBack(ROUTES.WORKSPACE_INVITE.getRoute(this.props.route.params.policyID))} - /> - - - - {this.props.translate('common.privacy')} - - - } - > - - - - - {this.props.translate('workspace.inviteMessage.inviteMessagePrompt')} - - - (this.welcomeMessageInputRef = el)} - role={CONST.ACCESSIBILITY_ROLE.TEXT} - inputID="welcomeMessage" - label={this.props.translate('workspace.inviteMessage.personalMessagePrompt')} - accessibilityLabel={this.props.translate('workspace.inviteMessage.personalMessagePrompt')} - autoCompleteType="off" - autoCorrect={false} - autoGrowHeight - inputStyle={[this.props.themeStyles.verticalAlignTop]} - containerStyles={[this.props.themeStyles.autoGrowHeightMultilineInput]} - defaultValue={this.state.welcomeNote} - value={this.state.welcomeNote} - onChangeText={(text) => { - this.debouncedSaveDraf(text); - this.setState({welcomeNote: text}); - }} - /> - - - - - ); - } + + + + + {props.translate('workspace.inviteMessage.inviteMessagePrompt')} + + + { + saveDraft(text); + }} + /> + + + + + ); } WorkspaceInviteMessagePage.propTypes = propTypes; From 8164342608fa099725d8759f65f098b9b9449050 Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Mon, 11 Dec 2023 10:30:48 +0100 Subject: [PATCH 06/29] Update type imports --- src/pages/home/report/ReportActionItemMessage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/ReportActionItemMessage.tsx b/src/pages/home/report/ReportActionItemMessage.tsx index 4f70e98d44b9..81742a47dcde 100644 --- a/src/pages/home/report/ReportActionItemMessage.tsx +++ b/src/pages/home/report/ReportActionItemMessage.tsx @@ -5,8 +5,8 @@ import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; -import {ReportAction} from '@src/types/onyx'; -import {OriginalMessageAddComment} from '@src/types/onyx/OriginalMessage'; +import type {ReportAction} from '@src/types/onyx'; +import type {OriginalMessageAddComment} from '@src/types/onyx/OriginalMessage'; import ReportActionItemFragment from './ReportActionItemFragment'; type ReportActionItemMessageProps = { From bfbbe952acd05949f0cb295923d63743f5810f3a Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Mon, 11 Dec 2023 11:23:54 +0100 Subject: [PATCH 07/29] Add type predicate to isEmptyObject function --- src/libs/ReportUtils.ts | 8 ++++---- src/types/utils/EmptyObject.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index a6393780d2a7..53eaeb3d00b8 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1000,16 +1000,16 @@ function isOneOnOneChat(report: OnyxEntry): boolean { /** * Get the report given a reportID */ -function getReport(reportID: string | undefined): OnyxEntry { +function getReport(reportID: string | undefined): OnyxEntry | EmptyObject { /** * Using typical string concatenation here due to performance issues * with template literals. */ if (!allReports) { - return null; + return {}; } - return allReports?.[ONYXKEYS.COLLECTION.REPORT + reportID] ?? null; + return allReports?.[ONYXKEYS.COLLECTION.REPORT + reportID] ?? {}; } /** @@ -1922,7 +1922,7 @@ function getTransactionReportName(reportAction: OnyxEntry): string * @param [reportAction] This can be either a report preview action or the IOU action */ function getReportPreviewMessage( - report: OnyxEntry, + report: OnyxEntry | EmptyObject, reportAction: OnyxEntry | EmptyObject = {}, shouldConsiderReceiptBeingScanned = false, isPreviewMessageForParentChatReport = false, diff --git a/src/types/utils/EmptyObject.ts b/src/types/utils/EmptyObject.ts index 9b9f3244a5f8..8cb1ff6db2a2 100644 --- a/src/types/utils/EmptyObject.ts +++ b/src/types/utils/EmptyObject.ts @@ -7,7 +7,7 @@ function isNotEmptyObject | Falsy>(arg: T | Em return Object.keys(arg ?? {}).length > 0; } -function isEmptyObject(obj: T): boolean { +function isEmptyObject(obj: T | EmptyObject | null | undefined): obj is EmptyObject { return Object.keys(obj ?? {}).length === 0; } From 0b91ff11fe572dda9809b993cf8215694e9a2fdc Mon Sep 17 00:00:00 2001 From: cdOut <88325488+cdOut@users.noreply.github.com> Date: Mon, 11 Dec 2023 12:36:59 +0100 Subject: [PATCH 08/29] add timeoutRef clear on unmount --- src/pages/workspace/WorkspaceInviteMessagePage.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/pages/workspace/WorkspaceInviteMessagePage.js b/src/pages/workspace/WorkspaceInviteMessagePage.js index 7ae3e9f16897..164a2d6f2ffb 100644 --- a/src/pages/workspace/WorkspaceInviteMessagePage.js +++ b/src/pages/workspace/WorkspaceInviteMessagePage.js @@ -75,6 +75,13 @@ function WorkspaceInviteMessagePage(props) { const focusTimeoutRef = useRef(); const welcomeMessageInputRef = useRef(null); + useEffect( + () => () => { + clearTimeout(focusTimeoutRef.current); + }, + [], + ); + const focusWelcomeMessageInput = () => { focusTimeoutRef.current = setTimeout(() => { welcomeMessageInputRef.current.focus(); From e08d75e301efe84af1023d55ee32d353b57a89b1 Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Wed, 13 Dec 2023 09:46:19 +0100 Subject: [PATCH 09/29] Updates to follow main branch --- .../home/report/ReportActionItemMessage.tsx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/pages/home/report/ReportActionItemMessage.tsx b/src/pages/home/report/ReportActionItemMessage.tsx index 81742a47dcde..a095115eff88 100644 --- a/src/pages/home/report/ReportActionItemMessage.tsx +++ b/src/pages/home/report/ReportActionItemMessage.tsx @@ -7,6 +7,7 @@ import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import type {ReportAction} from '@src/types/onyx'; import type {OriginalMessageAddComment} from '@src/types/onyx/OriginalMessage'; +import TextCommentFragment from './comment/TextCommentFragment'; import ReportActionItemFragment from './ReportActionItemFragment'; type ReportActionItemMessageProps = { @@ -32,6 +33,21 @@ function ReportActionItemMessage({action, displayAsGroup, reportID, style, isHid const fragments = (action.previousMessage ?? action.message ?? []).filter((item) => !!item); const isIOUReport = ReportActionsUtils.isMoneyRequestAction(action); + + if (ReportActionsUtils.isMemberChangeAction(action)) { + const fragment = ReportActionsUtils.getMemberChangeMessageFragment(action); + + return ( + + ); + } + let iouMessage: string | undefined; if (isIOUReport) { const originalMessage = action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? action.originalMessage : null; From 6ad344fe14d96a9ae5aaf3d0eb13071432477245 Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Wed, 13 Dec 2023 09:59:09 +0100 Subject: [PATCH 10/29] Fix linter error --- src/types/onyx/OriginalMessage.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts index aa647649d418..aec355bf81b6 100644 --- a/src/types/onyx/OriginalMessage.ts +++ b/src/types/onyx/OriginalMessage.ts @@ -237,4 +237,16 @@ type OriginalMessage = | OriginalMessageMoved; export default OriginalMessage; -export type {ChronosOOOEvent, Decision, Reaction, ActionName, IOUMessage, Closed, OriginalMessageActionName, ChangeLog, OriginalMessageIOU, OriginalMessageCreated, OriginalMessageAddComment}; +export type { + ChronosOOOEvent, + Decision, + Reaction, + ActionName, + IOUMessage, + Closed, + OriginalMessageActionName, + ChangeLog, + OriginalMessageIOU, + OriginalMessageCreated, + OriginalMessageAddComment, +}; From d547223eeaa63a8381f1cfaae42f96817cb65703 Mon Sep 17 00:00:00 2001 From: cdOut <88325488+cdOut@users.noreply.github.com> Date: Thu, 14 Dec 2023 10:54:32 +0100 Subject: [PATCH 11/29] change code for auto focus input --- .../workspace/WorkspaceInviteMessagePage.js | 39 ++++++------------- 1 file changed, 11 insertions(+), 28 deletions(-) diff --git a/src/pages/workspace/WorkspaceInviteMessagePage.js b/src/pages/workspace/WorkspaceInviteMessagePage.js index 164a2d6f2ffb..c6b88216a1ee 100644 --- a/src/pages/workspace/WorkspaceInviteMessagePage.js +++ b/src/pages/workspace/WorkspaceInviteMessagePage.js @@ -1,7 +1,7 @@ import {isEmpty} from 'lodash'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, {useEffect, useRef} from 'react'; +import React from 'react'; import {Keyboard, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; @@ -17,10 +17,12 @@ import TextInput from '@components/TextInput'; import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import withNavigationFocus from '@components/withNavigationFocus'; import withThemeStyles, {withThemeStylesPropTypes} from '@components/withThemeStyles'; +import useAutoFocusInput from '@hooks/useAutoFocusInput'; import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; +import updateMultilineInputRange from '@libs/updateMultilineInputRange'; import * as Link from '@userActions/Link'; import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; @@ -72,33 +74,11 @@ const defaultProps = { }; function WorkspaceInviteMessagePage(props) { - const focusTimeoutRef = useRef(); - const welcomeMessageInputRef = useRef(null); + const {inputCallbackRef} = useAutoFocusInput(); - useEffect( - () => () => { - clearTimeout(focusTimeoutRef.current); - }, - [], - ); - - const focusWelcomeMessageInput = () => { - focusTimeoutRef.current = setTimeout(() => { - welcomeMessageInputRef.current.focus(); - // Below condition is needed for web, desktop and mweb only, for native cursor is set at end by default. - if (welcomeMessageInputRef.current.value && welcomeMessageInputRef.current.setSelectionRange) { - const length = welcomeMessageInputRef.current.value.length; - welcomeMessageInputRef.current.setSelectionRange(length, length); - } - }, CONST.ANIMATED_TRANSITION); - }; - - useEffect(() => { - if (_.isEmpty(props.invitedEmailsToAccountIDsDraft)) { - Navigation.goBack(ROUTES.WORKSPACE_INVITE.getRoute(props.route.params.policyID), true); - } - focusWelcomeMessageInput(); - }, [props.invitedEmailsToAccountIDsDraft, props.route.params.policyID]); + if (_.isEmpty(props.invitedEmailsToAccountIDsDraft)) { + Navigation.goBack(ROUTES.WORKSPACE_INVITE.getRoute(props.route.params.policyID), true); + } const saveDraft = (newDraft) => { Policy.setWorkspaceInviteMessageDraft(props.route.params.policyID, newDraft); @@ -209,7 +189,10 @@ function WorkspaceInviteMessagePage(props) { onChangeText={(text) => { saveDraft(text); }} - ref={(el) => (welcomeMessageInputRef.current = el)} + ref={(el) => { + updateMultilineInputRange(el); + inputCallbackRef(el); + }} /> From c5da0d20a95e69a81f7f5df1b7c1fa343c5f5122 Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Thu, 14 Dec 2023 17:11:29 +0100 Subject: [PATCH 12/29] Update isEmptyObject typing --- src/types/utils/EmptyObject.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/types/utils/EmptyObject.ts b/src/types/utils/EmptyObject.ts index 8cb1ff6db2a2..aa8b538499cd 100644 --- a/src/types/utils/EmptyObject.ts +++ b/src/types/utils/EmptyObject.ts @@ -2,12 +2,14 @@ import Falsy from './Falsy'; type EmptyObject = Record; +type EmptyValue = EmptyObject | null | undefined; + // eslint-disable-next-line rulesdir/no-negated-variables function isNotEmptyObject | Falsy>(arg: T | EmptyObject): arg is NonNullable { return Object.keys(arg ?? {}).length > 0; } -function isEmptyObject(obj: T | EmptyObject | null | undefined): obj is EmptyObject { +function isEmptyObject(obj: T | EmptyValue): obj is EmptyValue { return Object.keys(obj ?? {}).length === 0; } From 480bdfa77836f5072068b50e4078f9e722187192 Mon Sep 17 00:00:00 2001 From: Yauheni Pasiukevich Date: Thu, 7 Dec 2023 13:37:25 +0100 Subject: [PATCH 13/29] Migrate 'MoneyReportHeaderStatusBar.js' component to TypeScript --- src/ONYXKEYS.ts | 1 + ...sBar.js => MoneyReportHeaderStatusBar.tsx} | 14 ++--- src/libs/NextStepUtils.ts | 6 +- src/types/onyx/ReportNextStep.ts | 56 +++++++++++++++++++ src/types/onyx/index.ts | 2 + 5 files changed, 64 insertions(+), 15 deletions(-) rename src/components/{MoneyReportHeaderStatusBar.js => MoneyReportHeaderStatusBar.tsx} (80%) create mode 100644 src/types/onyx/ReportNextStep.ts diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 0cc7934ad007..f8d43748b0ae 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -460,6 +460,7 @@ type OnyxValues = { [ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS]: OnyxTypes.RecentlyUsedTags; [ONYXKEYS.COLLECTION.SELECTED_TAB]: string; [ONYXKEYS.COLLECTION.PRIVATE_NOTES_DRAFT]: string; + [ONYXKEYS.COLLECTION.NEXT_STEP]: OnyxTypes.ReportNextStep; // Forms [ONYXKEYS.FORMS.ADD_DEBIT_CARD_FORM]: OnyxTypes.AddDebitCardForm; diff --git a/src/components/MoneyReportHeaderStatusBar.js b/src/components/MoneyReportHeaderStatusBar.tsx similarity index 80% rename from src/components/MoneyReportHeaderStatusBar.js rename to src/components/MoneyReportHeaderStatusBar.tsx index 687bc92ffbcd..8eaf67e3c96a 100644 --- a/src/components/MoneyReportHeaderStatusBar.js +++ b/src/components/MoneyReportHeaderStatusBar.tsx @@ -3,20 +3,16 @@ import {Text, View} from 'react-native'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as NextStepUtils from '@libs/NextStepUtils'; -import nextStepPropTypes from '@pages/nextStepPropTypes'; import CONST from '@src/CONST'; +import ReportNextStep from '@src/types/onyx/ReportNextStep'; import RenderHTML from './RenderHTML'; -const propTypes = { +type MoneyReportHeaderStatusBarProps = { /** The next step for the report */ - nextStep: nextStepPropTypes, + nextStep: ReportNextStep; }; -const defaultProps = { - nextStep: {}, -}; - -function MoneyReportHeaderStatusBar({nextStep}) { +function MoneyReportHeaderStatusBar({nextStep}: MoneyReportHeaderStatusBarProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -38,7 +34,5 @@ function MoneyReportHeaderStatusBar({nextStep}) { } MoneyReportHeaderStatusBar.displayName = 'MoneyReportHeaderStatusBar'; -MoneyReportHeaderStatusBar.propTypes = propTypes; -MoneyReportHeaderStatusBar.defaultProps = defaultProps; export default MoneyReportHeaderStatusBar; diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts index b58f2af1eeac..f5031d476f20 100644 --- a/src/libs/NextStepUtils.ts +++ b/src/libs/NextStepUtils.ts @@ -1,9 +1,5 @@ import Str from 'expensify-common/lib/str'; - -type Message = { - text: string; - type?: string; -}; +import type {Message} from '@src/types/onyx/ReportNextStep'; function parseMessage(messages: Message[] | undefined) { let nextStepHTML = ''; diff --git a/src/types/onyx/ReportNextStep.ts b/src/types/onyx/ReportNextStep.ts new file mode 100644 index 000000000000..b619319d8f91 --- /dev/null +++ b/src/types/onyx/ReportNextStep.ts @@ -0,0 +1,56 @@ +type Message = { + text: string; + type?: string; + action?: string; +}; + +type DataOptions = { + canSeeACHOption?: boolean; + isManualReimbursementEnabled?: boolean; + maskedLockedAccountNumber?: string; + preferredWithdrawalDeleted?: boolean; +}; + +type Button = { + text?: string; + tooltip?: string; + disabled?: boolean; + hidden?: boolean; + data?: DataOptions; +}; + +type ReportNextStep = { + /** The message parts of the next step */ + message?: Message[]; + + /** The title for the next step */ + title?: string; + + /** Whether the user should take some sort of action in order to unblock the report */ + requiresUserAction?: boolean; + + /** The type of next step */ + type: 'alert' | 'neutral' | null; + + /** If the "Undo submit" button should be visible */ + showUndoSubmit?: boolean; + + /** Deprecated - If the next step should be displayed on mobile, related to OldApp */ + showForMobile?: boolean; + + /** If the next step should be displayed at the expense level */ + showForExpense?: boolean; + + /** An optional alternate message to display on expenses instead of what is provided in the "message" field */ + expenseMessage?: Message[]; + + /** The next person in the approval chain of the report */ + nextReceiver?: string; + + /** An array of buttons to be displayed next to the next step */ + buttons?: Record; +}; + +export default ReportNextStep; + +export type {Message}; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 110bdb024a8c..62e474753745 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -37,6 +37,7 @@ import ReportAction, {ReportActions} from './ReportAction'; import ReportActionReactions from './ReportActionReactions'; import ReportActionsDrafts from './ReportActionsDrafts'; import ReportMetadata from './ReportMetadata'; +import ReportNextStep from './ReportNextStep'; import ReportUserIsTyping from './ReportUserIsTyping'; import Request from './Request'; import Response from './Response'; @@ -107,6 +108,7 @@ export type { ReportActions, ReportActionsDrafts, ReportMetadata, + ReportNextStep, Request, Response, ScreenShareRequest, From d7b57a50710ed83bbf308b74b96cf91599efff7b Mon Sep 17 00:00:00 2001 From: cdOut <88325488+cdOut@users.noreply.github.com> Date: Fri, 15 Dec 2023 13:18:48 +0100 Subject: [PATCH 14/29] use hooks for translate and styles --- .../workspace/WorkspaceInviteMessagePage.js | 45 +++++++++---------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/src/pages/workspace/WorkspaceInviteMessagePage.js b/src/pages/workspace/WorkspaceInviteMessagePage.js index d8de68bd6cc2..14178c35fb70 100644 --- a/src/pages/workspace/WorkspaceInviteMessagePage.js +++ b/src/pages/workspace/WorkspaceInviteMessagePage.js @@ -14,10 +14,10 @@ import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeed import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import withNavigationFocus from '@components/withNavigationFocus'; -import withThemeStyles, {withThemeStylesPropTypes} from '@components/withThemeStyles'; import useAutoFocusInput from '@hooks/useAutoFocusInput'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; @@ -63,8 +63,6 @@ const propTypes = { }).isRequired, ...policyPropTypes, - ...withLocalizePropTypes, - ...withThemeStylesPropTypes, }; const defaultProps = { @@ -74,6 +72,9 @@ const defaultProps = { }; function WorkspaceInviteMessagePage(props) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const {inputCallbackRef} = useAutoFocusInput(); if (_.isEmpty(props.invitedEmailsToAccountIDsDraft)) { @@ -85,7 +86,7 @@ function WorkspaceInviteMessagePage(props) { }; const getDefaultWelcomeNote = () => - props.translate('workspace.inviteMessage.welcomeNote', { + translate('workspace.inviteMessage.welcomeNote', { workspaceName: props.policy.name, }); @@ -131,7 +132,7 @@ function WorkspaceInviteMessagePage(props) { onBackButtonPress={() => Navigation.goBack(ROUTES.SETTINGS_WORKSPACES)} > Navigation.goBack(ROUTES.WORKSPACE_INVITE.getRoute(props.route.params.policyID))} /> - - {props.translate('common.privacy')} + + {translate('common.privacy')} } > - + - - {props.translate('workspace.inviteMessage.inviteMessagePrompt')} + + {translate('workspace.inviteMessage.inviteMessagePrompt')} - + { saveDraft(text); @@ -205,7 +206,6 @@ WorkspaceInviteMessagePage.defaultProps = defaultProps; WorkspaceInviteMessagePage.displayName = 'WorkspaceInviteMessagePage'; export default compose( - withLocalize, withPolicyAndFullscreenLoading, withOnyx({ allPersonalDetails: { @@ -220,5 +220,4 @@ export default compose( }, }), withNavigationFocus, - withThemeStyles, )(WorkspaceInviteMessagePage); From 5712d5d10bcd1de3316c59c603c6c1061cc588a7 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Tue, 19 Dec 2023 15:55:09 +0530 Subject: [PATCH 15/29] collapse faq sections --- docs/_layouts/default.html | 19 +++++++------- docs/_sass/_main.scss | 25 ++++++++++++++++--- .../getting-started/Using-The-App.md | 17 ++++++++++++- docs/assets/js/main.js | 2 ++ 4 files changed, 49 insertions(+), 14 deletions(-) diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html index 7d98500ecf32..6b4532a749ad 100644 --- a/docs/_layouts/default.html +++ b/docs/_layouts/default.html @@ -64,17 +64,16 @@
{% if page.url contains "/articles/" %} -

- {{ page.name | remove: '.md' | split: "-" | join: " " }} -

- -
- {{ content }} -
+

+ {{ page.name | remove: '.md' | split: "-" | join: " " }} +

+
+ {{ content }} +
{% else %} -
- {{ content }} -
+
+ {{ content }} +
{% endif %}
diff --git a/docs/_sass/_main.scss b/docs/_sass/_main.scss index 9276443c3813..05378d7ce526 100644 --- a/docs/_sass/_main.scss +++ b/docs/_sass/_main.scss @@ -99,7 +99,8 @@ h2, h3, h4, h5, -h6 { +h6, +summary { color: $color-text; font-weight: bold; padding-bottom: 12px; @@ -113,7 +114,25 @@ h6 { margin-top: 20px; } -h1 { +details>summary { + list-style: none; +} + +// summary::-webkit-details-marker { +// display: block; +// } + +// summary::before { +// content: ' ►'; +// } + +// details[open] summary::before { +// content: " ▼"; +// } + + +h1, +summary { font-family: "ExpensifyNewKansas", "Helvetica Neue", "Helvetica", Arial, sans-serif; font-weight: 500; font-size: larger; @@ -398,7 +417,7 @@ button { flex-wrap: wrap; } - h1 { + h1, summary { font-size: 1.5em; padding: 20px 0 12px 0; } diff --git a/docs/articles/expensify-classic/getting-started/Using-The-App.md b/docs/articles/expensify-classic/getting-started/Using-The-App.md index 281a26a4317b..f77acd1864e1 100644 --- a/docs/articles/expensify-classic/getting-started/Using-The-App.md +++ b/docs/articles/expensify-classic/getting-started/Using-The-App.md @@ -2,8 +2,10 @@ title: Using the app description: Streamline expense management effortlessly with the Expensify mobile app. Learn how to install, enable push notifications, and use SmartScan to capture, categorize, and track expenses. Versatile for personal and business use, Expensify is a secure and automated solution for managing your finances on the go. --- + # Overview The Expensify mobile app is the ultimate expense management solution that makes it effortless to track and submit your receipts and expenses. Use the app to snap a picture of your receipts, categorize and submit expenses, and even review and approve expense reports. + # How to install the Expensify app To get started with Expensify on your mobile device, you need to download the app: 1. Visit the App Store (iOS) or Google Play Store (Android). @@ -11,6 +13,7 @@ To get started with Expensify on your mobile device, you need to download the ap 3. Tap "Download" or "Install." Once the app is installed, open it and log in with your Expensify credentials. If you don't have an Expensify account, you can create one during the sign-up process. + # How to enable on push notifications Push notifications keep you informed about expense approvals, reimbursements, and more. To enable push notifications: 1. Open the Expensify app. @@ -39,14 +42,26 @@ SmartScan's performance can vary depending on factors such as receipt quality, l **Receipt quality**: The clarity and condition of a receipt can impact SmartScan's accuracy. For best results, ensure your environment is well-lit and the receipt is straight and free of obstructions. **Language support**: While SmartScan supports multiple languages, its accuracy may differ from one language to another. Users dealing with non-English receipts should be aware of potential variations in data extraction. **Handwriting recognition**: Handwritten receipts might pose challenges for SmartScan. In such cases, manual verification may be necessary to ensure accurate data entry. -# FAQ + + +{::options parse_block_html="true" /} +
+{::options parse_block_html="false" /} +FAQ {: #faq} + ## Can I use the mobile app for both personal and business expenses? Yes, you can use Expensify for personal and business expenses. It's versatile and suitable for both individual and corporate use. Check out our personal and business plans [here](https://www.expensify.com/pricing) to see what might be right for you. + ## Is it possible to categorize and tag expenses on the mobile app? Yes, you can categorize and tag expenses on the mobile app. The app allows you to customize categories and tags to help organize and track your spending. + ## What should I do if I encounter issues with the mobile app, such as login problems or crashes? If you experience issues, first make sure you’re using the most recent version of the app. You can also try to restarting the app. If the issue persists, you can start a chat with Concierge in the app or write to [concierge@expensify.com](mailto:concierge@expensify.com). + ## Is the mobile app secure for managing sensitive financial information? Expensify takes security seriously and employs encryption and other security measures to protect your data. It's important to use strong, unique passwords and enable device security features like biometric authentication. + ## Can I use the mobile app offline, and will my data sync when I'm back online? Yes, you can use the mobile app offline to capture receipts and create expenses. The app will sync your data once you have an internet connection. + +
\ No newline at end of file diff --git a/docs/assets/js/main.js b/docs/assets/js/main.js index dde3af22e900..1bf7015eab07 100644 --- a/docs/assets/js/main.js +++ b/docs/assets/js/main.js @@ -190,6 +190,7 @@ window.addEventListener('DOMContentLoaded', () => { // Disable the collapsible functionality of the library by // setting the maximum number of heading levels (6) collapseDepth: 6, + headingSelector: 'h1, h2, h3, summary', // Main class to add to lists. listClass: 'lhn-items', @@ -226,6 +227,7 @@ window.addEventListener('DOMContentLoaded', () => { // the LHN menu in responsive view. lhnContent.addEventListener('click', (event) => { const clickedLink = event.target; + console.log(clickedLink) if (clickedLink) { const href = clickedLink.getAttribute('href'); if (href && href.startsWith('#') && !!document.getElementById(href.slice(1))) { From a17f3577cf3744386f4644c5d06602df655e320d Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Tue, 19 Dec 2023 16:18:54 +0530 Subject: [PATCH 16/29] fix arrow icon on chrome --- docs/_sass/_main.scss | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/docs/_sass/_main.scss b/docs/_sass/_main.scss index 05378d7ce526..309529a113c2 100644 --- a/docs/_sass/_main.scss +++ b/docs/_sass/_main.scss @@ -46,7 +46,6 @@ blockquote { article, aside, -details, figcaption, figure, footer, @@ -55,8 +54,7 @@ hgroup, main, menu, nav, -section, -summary { +section { display: block; } @@ -113,23 +111,10 @@ h5, h6 { margin-top: 20px; } - -details>summary { - list-style: none; -} - -// summary::-webkit-details-marker { -// display: block; -// } - -// summary::before { -// content: ' ►'; -// } - -// details[open] summary::before { -// content: " ▼"; -// } +details summary { + cursor: pointer; +} h1, summary { From d236f5237ab70e14a08201abacc0edfe3b966199 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Tue, 19 Dec 2023 16:21:12 +0530 Subject: [PATCH 17/29] undo indent --- docs/_layouts/default.html | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html index 6b4532a749ad..b050a4bbbc16 100644 --- a/docs/_layouts/default.html +++ b/docs/_layouts/default.html @@ -64,16 +64,16 @@
{% if page.url contains "/articles/" %} -

- {{ page.name | remove: '.md' | split: "-" | join: " " }} -

-
- {{ content }} -
+

+ {{ page.name | remove: '.md' | split: "-" | join: " " }} +

+
+ {{ content }} +
{% else %} -
- {{ content }} -
+
+ {{ content }} +
{% endif %}
From 09ce7b4a3db424f8bd4d1ccc3a81568b3cfbd692 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Tue, 19 Dec 2023 16:21:38 +0530 Subject: [PATCH 18/29] undo indent --- docs/_layouts/default.html | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html index b050a4bbbc16..2581530cb14f 100644 --- a/docs/_layouts/default.html +++ b/docs/_layouts/default.html @@ -67,6 +67,7 @@

{{ page.name | remove: '.md' | split: "-" | join: " " }}

+
{{ content }}
From 12008555fb8840a6cb1ddfbbf1f0d4be4cc02c18 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Tue, 19 Dec 2023 16:22:09 +0530 Subject: [PATCH 19/29] undo indent --- docs/_layouts/default.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html index 2581530cb14f..7d98500ecf32 100644 --- a/docs/_layouts/default.html +++ b/docs/_layouts/default.html @@ -67,7 +67,7 @@

{{ page.name | remove: '.md' | split: "-" | join: " " }}

- +
{{ content }}
From 6cc8b833b91f222cf6c5652ac8b14d23fae9cc3b Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Tue, 19 Dec 2023 16:23:25 +0530 Subject: [PATCH 20/29] rm console log --- docs/assets/js/main.js | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/assets/js/main.js b/docs/assets/js/main.js index 1bf7015eab07..6e154bb09a44 100644 --- a/docs/assets/js/main.js +++ b/docs/assets/js/main.js @@ -227,7 +227,6 @@ window.addEventListener('DOMContentLoaded', () => { // the LHN menu in responsive view. lhnContent.addEventListener('click', (event) => { const clickedLink = event.target; - console.log(clickedLink) if (clickedLink) { const href = clickedLink.getAttribute('href'); if (href && href.startsWith('#') && !!document.getElementById(href.slice(1))) { From 639f05a89805fe7ef609cdd35b51f940fe9881cf Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Tue, 19 Dec 2023 16:40:08 +0530 Subject: [PATCH 21/29] make faq unselectable --- docs/_sass/_main.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/_sass/_main.scss b/docs/_sass/_main.scss index 309529a113c2..a6298ae70395 100644 --- a/docs/_sass/_main.scss +++ b/docs/_sass/_main.scss @@ -114,6 +114,7 @@ h6 { details summary { cursor: pointer; + user-select: none; } h1, From d62872eeeebac8277e8905a612d16bd2864b60a3 Mon Sep 17 00:00:00 2001 From: cdOut <88325488+cdOut@users.noreply.github.com> Date: Tue, 19 Dec 2023 13:42:34 +0100 Subject: [PATCH 22/29] review fixes --- .../workspace/WorkspaceInviteMessagePage.js | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/pages/workspace/WorkspaceInviteMessagePage.js b/src/pages/workspace/WorkspaceInviteMessagePage.js index 14178c35fb70..7b7350823168 100644 --- a/src/pages/workspace/WorkspaceInviteMessagePage.js +++ b/src/pages/workspace/WorkspaceInviteMessagePage.js @@ -141,7 +141,7 @@ function WorkspaceInviteMessagePage(props) { onBackButtonPress={() => Navigation.goBack(ROUTES.WORKSPACE_INVITE.getRoute(props.route.params.policyID))} /> - - {translate('common.privacy')} + + {translate('common.privacy')} } > - + - + {translate('workspace.inviteMessage.inviteMessagePrompt')} - + { saveDraft(text); }} ref={(el) => { - updateMultilineInputRange(el); + if (!el) { + return; + } inputCallbackRef(el); + updateMultilineInputRange(el); }} /> From b4b2c57b32b9d3ba967e848f13a92f4199d5b517 Mon Sep 17 00:00:00 2001 From: cdOut <88325488+cdOut@users.noreply.github.com> Date: Tue, 19 Dec 2023 14:03:57 +0100 Subject: [PATCH 23/29] fix correct navigation on invite --- src/pages/workspace/WorkspaceInviteMessagePage.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/pages/workspace/WorkspaceInviteMessagePage.js b/src/pages/workspace/WorkspaceInviteMessagePage.js index 7b7350823168..260214066e3e 100644 --- a/src/pages/workspace/WorkspaceInviteMessagePage.js +++ b/src/pages/workspace/WorkspaceInviteMessagePage.js @@ -1,7 +1,7 @@ import {isEmpty} from 'lodash'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React from 'react'; +import React, {useEffect} from 'react'; import {Keyboard, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; @@ -77,9 +77,13 @@ function WorkspaceInviteMessagePage(props) { const {inputCallbackRef} = useAutoFocusInput(); - if (_.isEmpty(props.invitedEmailsToAccountIDsDraft)) { + useEffect(() => { + if (!_.isEmpty(props.invitedEmailsToAccountIDsDraft)) { + return; + } Navigation.goBack(ROUTES.WORKSPACE_INVITE.getRoute(props.route.params.policyID), true); - } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); const saveDraft = (newDraft) => { Policy.setWorkspaceInviteMessageDraft(props.route.params.policyID, newDraft); From 14d38208d4e28e30798f7b6990e7c3fe93c56a96 Mon Sep 17 00:00:00 2001 From: cdOut <88325488+cdOut@users.noreply.github.com> Date: Tue, 19 Dec 2023 16:11:03 +0100 Subject: [PATCH 24/29] fix input value and space placement --- .../workspace/WorkspaceInviteMessagePage.js | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/pages/workspace/WorkspaceInviteMessagePage.js b/src/pages/workspace/WorkspaceInviteMessagePage.js index 260214066e3e..d60f187fa7d2 100644 --- a/src/pages/workspace/WorkspaceInviteMessagePage.js +++ b/src/pages/workspace/WorkspaceInviteMessagePage.js @@ -1,7 +1,7 @@ import {isEmpty} from 'lodash'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, {useEffect} from 'react'; +import React, {useEffect, useState} from 'react'; import {Keyboard, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; @@ -75,30 +75,32 @@ function WorkspaceInviteMessagePage(props) { const styles = useThemeStyles(); const {translate} = useLocalize(); + const [welcomeNote, setWelcomeNote] = useState(); + const {inputCallbackRef} = useAutoFocusInput(); + const getDefaultWelcomeNote = () => + props.workspaceInviteMessageDraft || + translate('workspace.inviteMessage.welcomeNote', { + workspaceName: props.policy.name, + }); + useEffect(() => { if (!_.isEmpty(props.invitedEmailsToAccountIDsDraft)) { + setWelcomeNote(getDefaultWelcomeNote()); return; } Navigation.goBack(ROUTES.WORKSPACE_INVITE.getRoute(props.route.params.policyID), true); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - const saveDraft = (newDraft) => { + const debouncedSaveDraft = _.debounce((newDraft) => { Policy.setWorkspaceInviteMessageDraft(props.route.params.policyID, newDraft); - }; - - const getDefaultWelcomeNote = () => - translate('workspace.inviteMessage.welcomeNote', { - workspaceName: props.policy.name, - }); - - const welcomeMessage = props.workspaceInviteMessageDraft || getDefaultWelcomeNote(); + }); const sendInvitation = () => { Keyboard.dismiss(); - Policy.addMembersToWorkspace(props.invitedEmailsToAccountIDsDraft, welcomeMessage, props.route.params.policyID); + Policy.addMembersToWorkspace(props.invitedEmailsToAccountIDsDraft, welcomeNote, props.route.params.policyID); Policy.setWorkspaceInviteMembersDraft(props.route.params.policyID, {}); SearchInputManager.searchInput = ''; // Pop the invite message page before navigating to the members page. @@ -187,11 +189,12 @@ function WorkspaceInviteMessagePage(props) { autoCompleteType="off" autoCorrect={false} autoGrowHeight - inputStyle={[styles.verticalAlignTop]} containerStyles={[styles.autoGrowHeightMultilineInput]} - value={welcomeMessage} + defaultValue={getDefaultWelcomeNote()} + value={welcomeNote} onChangeText={(text) => { - saveDraft(text); + setWelcomeNote(text); + debouncedSaveDraft(text); }} ref={(el) => { if (!el) { From 83840cd38de1c0edfd599bb13506ebba8ed0756c Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Tue, 19 Dec 2023 22:49:34 +0530 Subject: [PATCH 25/29] use arrow icon --- docs/_sass/_main.scss | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/_sass/_main.scss b/docs/_sass/_main.scss index a6298ae70395..cfdf4ff3a2bc 100644 --- a/docs/_sass/_main.scss +++ b/docs/_sass/_main.scss @@ -111,12 +111,24 @@ h5, h6 { margin-top: 20px; } - + +#faq::marker { + font-size: 1.5em; +} + details summary { cursor: pointer; user-select: none; } +details > summary { + list-style-image: url("/assets/images/arrow-right.svg"); +} + +details[open] > summary { + list-style-image: url("/assets/images/down.svg"); +} + h1, summary { font-family: "ExpensifyNewKansas", "Helvetica Neue", "Helvetica", Arial, sans-serif; From 361b77420daae1ac1fa8a6b6ae0003382651659c Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Tue, 19 Dec 2023 23:27:43 +0530 Subject: [PATCH 26/29] simplify api to use faq --- docs/_includes/faq-begin.md | 4 ++++ docs/_includes/faq-end.md | 1 + 2 files changed, 5 insertions(+) create mode 100644 docs/_includes/faq-begin.md create mode 100644 docs/_includes/faq-end.md diff --git a/docs/_includes/faq-begin.md b/docs/_includes/faq-begin.md new file mode 100644 index 000000000000..af1981c242cd --- /dev/null +++ b/docs/_includes/faq-begin.md @@ -0,0 +1,4 @@ +{::options parse_block_html="true" /} +
+{::options parse_block_html="false" /} +FAQ {: #faq} \ No newline at end of file diff --git a/docs/_includes/faq-end.md b/docs/_includes/faq-end.md new file mode 100644 index 000000000000..32012c1ab5cb --- /dev/null +++ b/docs/_includes/faq-end.md @@ -0,0 +1 @@ +
\ No newline at end of file From c420b43901aa0cd249a20cdc2647a1cfc467cb48 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Tue, 19 Dec 2023 23:27:49 +0530 Subject: [PATCH 27/29] simplify api to use faq --- .../expensify-classic/getting-started/Using-The-App.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/docs/articles/expensify-classic/getting-started/Using-The-App.md b/docs/articles/expensify-classic/getting-started/Using-The-App.md index f77acd1864e1..7d3a8cb5e22b 100644 --- a/docs/articles/expensify-classic/getting-started/Using-The-App.md +++ b/docs/articles/expensify-classic/getting-started/Using-The-App.md @@ -43,11 +43,7 @@ SmartScan's performance can vary depending on factors such as receipt quality, l **Language support**: While SmartScan supports multiple languages, its accuracy may differ from one language to another. Users dealing with non-English receipts should be aware of potential variations in data extraction. **Handwriting recognition**: Handwritten receipts might pose challenges for SmartScan. In such cases, manual verification may be necessary to ensure accurate data entry. - -{::options parse_block_html="true" /} -
-{::options parse_block_html="false" /} -FAQ {: #faq} +{% include faq-begin.md %} ## Can I use the mobile app for both personal and business expenses? Yes, you can use Expensify for personal and business expenses. It's versatile and suitable for both individual and corporate use. Check out our personal and business plans [here](https://www.expensify.com/pricing) to see what might be right for you. @@ -64,4 +60,4 @@ Expensify takes security seriously and employs encryption and other security mea ## Can I use the mobile app offline, and will my data sync when I'm back online? Yes, you can use the mobile app offline to capture receipts and create expenses. The app will sync your data once you have an internet connection. -
\ No newline at end of file +{% include faq-end.md %} \ No newline at end of file From 96c558c33c26a237e3b1a1513d398fcab01b58bc Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Tue, 19 Dec 2023 23:29:46 +0530 Subject: [PATCH 28/29] update template --- docs/TEMPLATE.md | 3 ++- .../expensify-classic/getting-started/Using-The-App.md | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/TEMPLATE.md b/docs/TEMPLATE.md index 4408e56382a2..bbf4ab96d35f 100644 --- a/docs/TEMPLATE.md +++ b/docs/TEMPLATE.md @@ -21,7 +21,7 @@ What options does a user have then interacting with this feature? What elements of this feature are pay-walled vs. free? --> -# FAQ +{% include faq-begin.md %} +{% include faq-end.md %} # Deep Dive