From 107f84cde8e3b3b8be70a51f79ecf8fb8201eab7 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 2 Feb 2024 15:32:38 +0100 Subject: [PATCH 001/600] implement isPolicyOwner --- src/libs/PolicyUtils.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index b6ee4ab3a353..80f23fd3c9ab 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -111,6 +111,11 @@ const isPolicyAdmin = (policy: OnyxEntry): boolean => policy?.role === C const isPolicyMember = (policyID: string, policies: Record): boolean => Object.values(policies).some((policy) => policy?.id === policyID); +/** + * Checks if the current user is an owner (creator) of the policy. + */ +const isPolicyOwner = (policy: OnyxEntry, currentUserAccountID: number): boolean => policy?.ownerAccountID === currentUserAccountID; + /** * Create an object mapping member emails to their accountIDs. Filter for members without errors, and get the login email from the personalDetail object using the accountID. * @@ -244,6 +249,7 @@ export { getCleanedTagName, isPendingDeletePolicy, isPolicyMember, + isPolicyOwner, isPaidGroupPolicy, extractPolicyIDFromPath, getPathWithoutPolicyID, From 41110392095b4762c020f66bd4baebeeb57ad171 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 2 Feb 2024 15:32:46 +0100 Subject: [PATCH 002/600] implement isReportOwner --- src/libs/ReportUtils.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 5f3efcbcdbb0..33ef7982af3c 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -4679,6 +4679,10 @@ function canBeAutoReimbursed(report: OnyxEntry, policy: OnyxEntry): boolean { + return report?.ownerAccountID === currentUserPersonalDetails?.accountID; +} + export { getReportParticipantsTitle, isReportMessageAttachment, @@ -4865,6 +4869,7 @@ export { isReportFieldOfTypeTitle, isReportFieldDisabled, getAvailableReportFields, + isReportOwner, }; export type { From 52b16ceb740c3f21c4b9c3629643db1ef4b8e52b Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 2 Feb 2024 15:45:47 +0100 Subject: [PATCH 003/600] implement draft types for leave API --- src/libs/API/parameters/LeavePolicyExpenseChatParams.ts | 6 ++++++ src/libs/API/parameters/LeaveWorkspaceParams.ts | 6 ++++++ src/libs/API/parameters/index.ts | 2 ++ src/libs/API/types.ts | 5 +++++ 4 files changed, 19 insertions(+) create mode 100644 src/libs/API/parameters/LeavePolicyExpenseChatParams.ts create mode 100644 src/libs/API/parameters/LeaveWorkspaceParams.ts diff --git a/src/libs/API/parameters/LeavePolicyExpenseChatParams.ts b/src/libs/API/parameters/LeavePolicyExpenseChatParams.ts new file mode 100644 index 000000000000..665b28b9e0a2 --- /dev/null +++ b/src/libs/API/parameters/LeavePolicyExpenseChatParams.ts @@ -0,0 +1,6 @@ +type LeavePolicyExpenseChatParams = { + // TODO: Clarify + reportID: string; +}; + +export default LeavePolicyExpenseChatParams; diff --git a/src/libs/API/parameters/LeaveWorkspaceParams.ts b/src/libs/API/parameters/LeaveWorkspaceParams.ts new file mode 100644 index 000000000000..b28fa3e5aed9 --- /dev/null +++ b/src/libs/API/parameters/LeaveWorkspaceParams.ts @@ -0,0 +1,6 @@ +type LeaveWorkspaceParams = { + // TODO: Clarify + policyID: string; +}; + +export default LeaveWorkspaceParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 8c0c2fde17cf..9e877fffbd2e 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -89,7 +89,9 @@ export type {default as AddWorkspaceRoomParams} from './AddWorkspaceRoomParams'; export type {default as UpdatePolicyRoomNameParams} from './UpdatePolicyRoomNameParams'; export type {default as AddEmojiReactionParams} from './AddEmojiReactionParams'; export type {default as RemoveEmojiReactionParams} from './RemoveEmojiReactionParams'; +export type {default as LeavePolicyExpenseChatParams} from './LeavePolicyExpenseChatParams'; export type {default as LeaveRoomParams} from './LeaveRoomParams'; +export type {default as LeaveWorkspaceParams} from './LeaveWorkspaceParams'; export type {default as InviteToRoomParams} from './InviteToRoomParams'; export type {default as RemoveFromRoomParams} from './RemoveFromRoomParams'; export type {default as FlagCommentParams} from './FlagCommentParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 05b658ee0702..37ed746ea5b6 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -115,6 +115,9 @@ const WRITE_COMMANDS = { SET_NAME_VALUE_PAIR: 'SetNameValuePair', SET_REPORT_FIELD: 'Report_SetFields', SET_REPORT_NAME: 'RenameReport', + // TODO: Clarify + LEAVE_WORKSPACE: 'LeaveWorkspace', + LEAVE_POLICY_EXPENSE_CHAT: 'LeaveWorkspaceExpenseChat', } as const; type WriteCommand = ValueOf; @@ -227,6 +230,8 @@ type WriteCommandParameters = { [WRITE_COMMANDS.SET_NAME_VALUE_PAIR]: Parameters.SetNameValuePairParams; [WRITE_COMMANDS.SET_REPORT_FIELD]: Parameters.SetReportFieldParams; [WRITE_COMMANDS.SET_REPORT_NAME]: Parameters.SetReportNameParams; + [WRITE_COMMANDS.LEAVE_WORKSPACE]: Parameters.LeaveWorkspaceParams; + [WRITE_COMMANDS.LEAVE_POLICY_EXPENSE_CHAT]: Parameters.LeavePolicyExpenseChatParams; }; const READ_COMMANDS = { From 5788e867e4ebd40cde8099d19f72332206842a92 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 2 Feb 2024 15:46:04 +0100 Subject: [PATCH 004/600] implement draft for leave API --- src/libs/actions/Policy.ts | 47 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 0c3a8afc1576..c68bd947faf9 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ import {PUBLIC_DOMAINS} from 'expensify-common/lib/CONST'; import ExpensiMark from 'expensify-common/lib/ExpensiMark'; import Str from 'expensify-common/lib/str'; @@ -14,6 +15,8 @@ import type { DeleteMembersFromWorkspaceParams, DeleteWorkspaceAvatarParams, DeleteWorkspaceParams, + LeavePolicyExpenseChatParams, + LeaveWorkspaceParams, OpenDraftWorkspaceRequestParams, OpenWorkspaceInvitePageParams, OpenWorkspaceMembersPageParams, @@ -1984,6 +1987,48 @@ function createWorkspaceFromIOUPayment(iouReport: Report): string | undefined { return policyID; } +/** + * TODO: Comment + */ +function leaveWorkspace(policyID: string) { + const optimisticData: OnyxUpdate[] = []; + const successData: OnyxUpdate[] = []; + const failureData: OnyxUpdate[] = []; + + const parameters: LeaveWorkspaceParams = { + policyID, + }; + + console.group('leaveWorkspace'); + console.log('policyID', policyID); + console.log('parameters', parameters); + console.log('{optimisticData, successData, failureData}', {optimisticData, successData, failureData}); + console.groupEnd(); + return; + API.write(WRITE_COMMANDS.LEAVE_WORKSPACE, parameters, {optimisticData, successData, failureData}); +} + +/** + * TODO: Comment + */ +function leavePolicyExpenseChat(reportID: string) { + const optimisticData: OnyxUpdate[] = []; + const successData: OnyxUpdate[] = []; + const failureData: OnyxUpdate[] = []; + + const parameters: LeavePolicyExpenseChatParams = { + reportID, + }; + + console.group('leaveWorkspace'); + console.log('reportID', reportID); + console.log('parameters', parameters); + console.log('{optimisticData, successData, failureData}', {optimisticData, successData, failureData}); + console.groupEnd(); + return; + API.write(WRITE_COMMANDS.LEAVE_POLICY_EXPENSE_CHAT, parameters, {optimisticData, successData, failureData}); +} + export { removeMembers, addMembersToWorkspace, @@ -2020,4 +2065,6 @@ export { buildOptimisticPolicyRecentlyUsedTags, createDraftInitialWorkspace, setWorkspaceInviteMessageDraft, + leaveWorkspace, + leavePolicyExpenseChat, }; From 2114adb61d998fb729101975b9a7d765355f9d57 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 2 Feb 2024 15:46:23 +0100 Subject: [PATCH 005/600] implement draft for leave a workspace chat --- src/pages/home/HeaderView.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js index ca4c90b2df55..0ea2f92d3cce 100644 --- a/src/pages/home/HeaderView.js +++ b/src/pages/home/HeaderView.js @@ -29,10 +29,12 @@ import {getGroupChatName} from '@libs/GroupChatUtils'; import * as HeaderUtils from '@libs/HeaderUtils'; import reportWithoutHasDraftSelector from '@libs/OnyxSelectors/reportWithoutHasDraftSelector'; import * as OptionsListUtils from '@libs/OptionsListUtils'; +import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import reportPropTypes from '@pages/reportPropTypes'; import * as Link from '@userActions/Link'; +import * as Policy from '@userActions/Policy'; import * as Report from '@userActions/Report'; import * as Session from '@userActions/Session'; import * as Task from '@userActions/Task'; @@ -118,6 +120,8 @@ function HeaderView(props) { const isUserCreatedPolicyRoom = ReportUtils.isUserCreatedPolicyRoom(props.report); const isPolicyMember = useMemo(() => !_.isEmpty(props.policy), [props.policy]); const canLeaveRoom = ReportUtils.canLeaveRoom(props.report, isPolicyMember); + const canLeavePolicyExpenseChat = + isPolicyExpenseChat && !(PolicyUtils.isPolicyAdmin(props.policy) || PolicyUtils.isPolicyOwner(props.policy, props.session.accountID) || ReportUtils.isReportOwner(props.report)); const isArchivedRoom = ReportUtils.isArchivedRoom(props.report); // We hide the button when we are chatting with an automated Expensify account since it's not possible to contact @@ -156,9 +160,9 @@ function HeaderView(props) { ), ); - const canJoinOrLeave = isChatThread || isUserCreatedPolicyRoom || canLeaveRoom; + const canJoinOrLeave = isChatThread || isUserCreatedPolicyRoom || canLeaveRoom || canLeavePolicyExpenseChat; const canJoin = canJoinOrLeave && !isWhisperAction && props.report.notificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN; - const canLeave = canJoinOrLeave && ((isChatThread && props.report.notificationPreference.length) || isUserCreatedPolicyRoom || canLeaveRoom); + const canLeave = canJoinOrLeave && ((isChatThread && props.report.notificationPreference.length) || isUserCreatedPolicyRoom || canLeaveRoom || canLeavePolicyExpenseChat); if (canJoin) { threeDotMenuItems.push({ icon: Expensicons.ChatBubbles, @@ -167,10 +171,11 @@ function HeaderView(props) { }); } else if (canLeave) { const isWorkspaceMemberLeavingWorkspaceRoom = !isChatThread && lodashGet(props.report, 'visibility', '') === CONST.REPORT.VISIBILITY.RESTRICTED && isPolicyMember; + const action = isPolicyExpenseChat ? () => Policy.leavePolicyExpenseChat(props.reportID) : () => Report.leaveRoom(props.reportID, isWorkspaceMemberLeavingWorkspaceRoom); threeDotMenuItems.push({ icon: Expensicons.ChatBubbles, text: translate('common.leave'), - onSelected: Session.checkIfActionIsAllowed(() => Report.leaveRoom(props.reportID, isWorkspaceMemberLeavingWorkspaceRoom)), + onSelected: Session.checkIfActionIsAllowed(action), }); } @@ -360,7 +365,7 @@ export default memo( }, policy: { key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : '0'}`, - selector: (policy) => _.pick(policy, ['name', 'avatar', 'pendingAction']), + selector: (policy) => _.pick(policy, ['name', 'avatar', 'pendingAction', 'role', 'ownerAccountID']), }, rootParentReportPolicy: { key: ({report}) => { From eb6299247215dff7c64c877273acb0f3949552aa Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 2 Feb 2024 15:46:31 +0100 Subject: [PATCH 006/600] implement draft for leave a workspace --- src/pages/workspace/WorkspacesListPage.tsx | 23 +++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/pages/workspace/WorkspacesListPage.tsx b/src/pages/workspace/WorkspacesListPage.tsx index 9b763120b30d..c8e518429557 100755 --- a/src/pages/workspace/WorkspacesListPage.tsx +++ b/src/pages/workspace/WorkspacesListPage.tsx @@ -28,11 +28,12 @@ import * as ReportUtils from '@libs/ReportUtils'; import type {AvatarSource} from '@libs/UserUtils'; import * as App from '@userActions/App'; import * as Policy from '@userActions/Policy'; +import * as Session from '@userActions/Session'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; -import type {PolicyMembers, Policy as PolicyType, ReimbursementAccount, Report} from '@src/types/onyx'; +import type {PolicyMembers, Policy as PolicyType, ReimbursementAccount, Report, Session as SessionType} from '@src/types/onyx'; import type * as OnyxCommon from '@src/types/onyx/OnyxCommon'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import withPolicyAndFullscreenLoading from './withPolicyAndFullscreenLoading'; @@ -74,6 +75,9 @@ type WorkspaceListPageOnyxProps = { /** All reports shared with the user (coming from Onyx) */ reports: OnyxCollection; + + /** Session info for the currently logged in user. */ + session: OnyxEntry; }; type WorkspaceListPageProps = WithPolicyAndFullscreenLoadingProps & WorkspaceListPageOnyxProps; @@ -109,7 +113,7 @@ function dismissWorkspaceError(policyID: string, pendingAction: OnyxCommon.Pendi throw new Error('Not implemented'); } -function WorkspacesListPage({policies, allPolicyMembers, reimbursementAccount, reports}: WorkspaceListPageProps) { +function WorkspacesListPage({policies, allPolicyMembers, reimbursementAccount, reports, session}: WorkspaceListPageProps) { const theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -135,6 +139,7 @@ function WorkspacesListPage({policies, allPolicyMembers, reimbursementAccount, r const getMenuItem = useCallback( ({item, index}: GetMenuItem) => { const isAdmin = item.role === CONST.POLICY.ROLE.ADMIN; + const isOwner = item.ownerAccountID === session?.accountID; // Menu options to navigate to the chat report of #admins and #announce room. // For navigation, the chat report ids may be unavailable due to the missing chat reports in Onyx. // In such cases, let us use the available chat report ids from the policy. @@ -168,6 +173,15 @@ function WorkspacesListPage({policies, allPolicyMembers, reimbursementAccount, r }); } + if (!(isAdmin || isOwner)) { + threeDotsMenuItems.push({ + icon: Expensicons.ChatBubbles, + text: translate('common.leave'), + // TODO: Integrate a handler + onSelected: Session.checkIfActionIsAllowed(() => Policy.leaveWorkspace(item.policyID ?? '')), + }); + } + return ( ); }, - [isSmallScreenWidth, styles.mb3, styles.mh5, styles.ph5, styles.hoveredComponentBG, translate], + [session?.accountID, styles.ph5, styles.mh5, styles.mb3, styles.hoveredComponentBG, translate, isSmallScreenWidth], ); const listHeaderComponent = useCallback(() => { @@ -409,5 +423,8 @@ export default withPolicyAndFullscreenLoading( reports: { key: ONYXKEYS.COLLECTION.REPORT, }, + session: { + key: ONYXKEYS.SESSION, + }, })(WorkspacesListPage), ); From 3775ad084b4ed838a7b4ad32793c2d9568ecc9e1 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 2 Feb 2024 17:53:42 +0100 Subject: [PATCH 007/600] integrate draft api data --- .../AppNavigator/ReportScreenIDSetter.ts | 26 ++------- src/libs/ReportUtils.ts | 48 +++++++++++++++- src/libs/actions/Policy.ts | 56 +++++++++++++++---- src/libs/actions/Report.ts | 42 +------------- 4 files changed, 95 insertions(+), 77 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts b/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts index b4bb56262860..927e4c57d66b 100644 --- a/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts +++ b/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts @@ -7,7 +7,7 @@ import {getPolicyMembersByIdWithoutCurrentUser} from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as App from '@userActions/App'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Policy, PolicyMembers, Report, ReportMetadata} from '@src/types/onyx'; +import type {Policy, PolicyMembers, Report} from '@src/types/onyx'; import type {ReportScreenWrapperProps} from './ReportScreenWrapper'; type ReportScreenIDSetterComponentProps = { @@ -23,9 +23,6 @@ type ReportScreenIDSetterComponentProps = { /** Whether user is a new user */ isFirstTimeNewExpensifyUser: OnyxEntry; - /** The report metadata */ - reportMetadata: OnyxCollection; - /** The accountID of the current user */ accountID?: number; }; @@ -41,25 +38,15 @@ const getLastAccessedReportID = ( policies: OnyxCollection, isFirstTimeNewExpensifyUser: OnyxEntry, openOnAdminRoom: boolean, - reportMetadata: OnyxCollection, policyID?: string, policyMemberAccountIDs?: number[], ): string | undefined => { - const lastReport = ReportUtils.findLastAccessedReport( - reports, - ignoreDefaultRooms, - policies, - !!isFirstTimeNewExpensifyUser, - openOnAdminRoom, - reportMetadata, - policyID, - policyMemberAccountIDs, - ); + const lastReport = ReportUtils.findLastAccessedReport(reports, ignoreDefaultRooms, policies, !!isFirstTimeNewExpensifyUser, openOnAdminRoom, policyID, policyMemberAccountIDs); return lastReport?.reportID; }; // This wrapper is reponsible for opening the last accessed report if there is no reportID specified in the route params -function ReportScreenIDSetter({route, reports, policies, policyMembers = {}, navigation, isFirstTimeNewExpensifyUser = false, reportMetadata, accountID}: ReportScreenIDSetterProps) { +function ReportScreenIDSetter({route, reports, policies, policyMembers = {}, navigation, isFirstTimeNewExpensifyUser = false, accountID}: ReportScreenIDSetterProps) { const {canUseDefaultRooms} = usePermissions(); const {activeWorkspaceID} = useActiveWorkspace(); @@ -84,7 +71,6 @@ function ReportScreenIDSetter({route, reports, policies, policyMembers = {}, nav policies, isFirstTimeNewExpensifyUser, !!reports?.params?.openOnAdminRoom, - reportMetadata, activeWorkspaceID, policyMemberAccountIDs, ); @@ -96,7 +82,7 @@ function ReportScreenIDSetter({route, reports, policies, policyMembers = {}, nav } else { App.confirmReadyToOpenApp(); } - }, [route, navigation, reports, canUseDefaultRooms, policies, isFirstTimeNewExpensifyUser, reportMetadata, activeWorkspaceID, policyMembers, accountID]); + }, [route, navigation, reports, canUseDefaultRooms, policies, isFirstTimeNewExpensifyUser, activeWorkspaceID, policyMembers, accountID]); // The ReportScreen without the reportID set will display a skeleton // until the reportID is loaded and set in the route param @@ -122,10 +108,6 @@ export default withOnyx session?.accountID, diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 33ef7982af3c..3f48f550a077 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -491,6 +491,13 @@ Onyx.connect({ }, }); +let reportMetadata: OnyxCollection = {}; +Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT_METADATA, + waitForCollectionCallback: true, + callback: (value) => (reportMetadata = value), +}); + function getChatType(report: OnyxEntry): ValueOf | undefined { return report?.chatType; } @@ -911,7 +918,7 @@ function filterReportsByPolicyIDAndMemberAccountIDs(reports: Report[], policyMem /** * Given an array of reports, return them sorted by the last read timestamp. */ -function sortReportsByLastRead(reports: Report[], reportMetadata: OnyxCollection): Array> { +function sortReportsByLastRead(reports: Report[]): Array> { return reports .filter((report) => !!report?.reportID && !!(reportMetadata?.[`${ONYXKEYS.COLLECTION.REPORT_METADATA}${report.reportID}`]?.lastVisitTime ?? report?.lastReadTime)) .sort((a, b) => { @@ -991,7 +998,6 @@ function findLastAccessedReport( policies: OnyxCollection, isFirstTimeNewExpensifyUser: boolean, openOnAdminRoom = false, - reportMetadata: OnyxCollection = {}, policyID?: string, policyMemberAccountIDs: number[] = [], ): OnyxEntry { @@ -1007,7 +1013,7 @@ function findLastAccessedReport( reportsValues = filterReportsByPolicyIDAndMemberAccountIDs(reportsValues, policyMemberAccountIDs, policyID); } - let sortedReports = sortReportsByLastRead(reportsValues, reportMetadata); + let sortedReports = sortReportsByLastRead(reportsValues); let adminReport: OnyxEntry | undefined; if (openOnAdminRoom) { @@ -4683,6 +4689,41 @@ function isReportOwner(report: OnyxEntry): boolean { return report?.ownerAccountID === currentUserPersonalDetails?.accountID; } +function navigateUserOnceLeaveReport(reportID: string) { + const sortedReportsByLastRead = sortReportsByLastRead(Object.values(allReports ?? {}) as Report[]); + + // We want to filter out the current report, hidden reports and empty chats + const filteredReportsByLastRead = sortedReportsByLastRead.filter( + (sortedReport) => + sortedReport?.reportID !== reportID && + sortedReport?.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN && + shouldReportBeInOptionList({ + report: sortedReport, + currentReportId: '', + isInGSDMode: false, + betas: [], + policies: {}, + excludeEmptyChats: true, + doesReportHaveViolations: false, + }), + ); + const lastAccessedReportID = filteredReportsByLastRead.at(-1)?.reportID; + + if (lastAccessedReportID) { + // We should call Navigation.goBack to pop the current route first before navigating to Concierge. + Navigation.goBack(ROUTES.HOME); + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(lastAccessedReportID)); + } else { + const participantAccountIDs = PersonalDetailsUtils.getAccountIDsByLogins([CONST.EMAIL.CONCIERGE]); + const chat = getChatByParticipants(participantAccountIDs); + if (chat?.reportID) { + // We should call Navigation.goBack to pop the current route first before navigating to Concierge. + Navigation.goBack(ROUTES.HOME); + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(chat.reportID)); + } + } +} + export { getReportParticipantsTitle, isReportMessageAttachment, @@ -4870,6 +4911,7 @@ export { isReportFieldDisabled, getAvailableReportFields, isReportOwner, + navigateUserOnceLeaveReport, }; export type { diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index c68bd947faf9..929318ed9856 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -1991,30 +1991,60 @@ function createWorkspaceFromIOUPayment(iouReport: Report): string | undefined { * TODO: Comment */ function leaveWorkspace(policyID: string) { - const optimisticData: OnyxUpdate[] = []; - const successData: OnyxUpdate[] = []; - const failureData: OnyxUpdate[] = []; - + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + avatar: '', + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + errors: null, + }, + }, + ]; const parameters: LeaveWorkspaceParams = { policyID, }; console.group('leaveWorkspace'); console.log('policyID', policyID); - console.log('parameters', parameters); - console.log('{optimisticData, successData, failureData}', {optimisticData, successData, failureData}); + console.log('{parameters, optimisticData}', {parameters, optimisticData}); console.groupEnd(); - return; - API.write(WRITE_COMMANDS.LEAVE_WORKSPACE, parameters, {optimisticData, successData, failureData}); + + API.write(WRITE_COMMANDS.LEAVE_WORKSPACE, parameters, {optimisticData}); } /** * TODO: Comment */ function leavePolicyExpenseChat(reportID: string) { - const optimisticData: OnyxUpdate[] = []; - const successData: OnyxUpdate[] = []; - const failureData: OnyxUpdate[] = []; + const report = ReportUtils.getReport(reportID); + + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, + value: { + notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, + }, + }, + ]; + const successData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, + value: { + notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, + }, + }, + ]; + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, + value: report, + }, + ]; const parameters: LeavePolicyExpenseChatParams = { reportID, @@ -2025,8 +2055,10 @@ function leavePolicyExpenseChat(reportID: string) { console.log('parameters', parameters); console.log('{optimisticData, successData, failureData}', {optimisticData, successData, failureData}); console.groupEnd(); - return; + API.write(WRITE_COMMANDS.LEAVE_POLICY_EXPENSE_CHAT, parameters, {optimisticData, successData, failureData}); + + ReportUtils.navigateUserOnceLeaveReport(reportID); } export { diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 782cf2b174c2..27cc7a2af4a7 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -66,7 +66,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; import ROUTES from '@src/ROUTES'; -import type {PersonalDetails, PersonalDetailsList, PolicyReportField, RecentlyUsedReportFields, ReportActionReactions, ReportMetadata, ReportUserIsTyping} from '@src/types/onyx'; +import type {PersonalDetails, PersonalDetailsList, PolicyReportField, RecentlyUsedReportFields, ReportActionReactions, ReportUserIsTyping} from '@src/types/onyx'; import type {Decision, OriginalMessageIOU} from '@src/types/onyx/OriginalMessage'; import type {NotificationPreference, WriteCapability} from '@src/types/onyx/Report'; import type Report from '@src/types/onyx/Report'; @@ -159,13 +159,6 @@ Onyx.connect({ }, }); -let reportMetadata: OnyxCollection = {}; -Onyx.connect({ - key: ONYXKEYS.COLLECTION.REPORT_METADATA, - waitForCollectionCallback: true, - callback: (value) => (reportMetadata = value), -}); - const allReports: OnyxCollection = {}; let conciergeChatReportID: string | undefined; const typingWatchTimers: Record = {}; @@ -2248,38 +2241,7 @@ function leaveRoom(reportID: string, isWorkspaceMemberLeavingWorkspaceRoom = fal API.write(WRITE_COMMANDS.LEAVE_ROOM, parameters, {optimisticData, successData, failureData}); - const sortedReportsByLastRead = ReportUtils.sortReportsByLastRead(Object.values(allReports ?? {}) as Report[], reportMetadata); - - // We want to filter out the current report, hidden reports and empty chats - const filteredReportsByLastRead = sortedReportsByLastRead.filter( - (sortedReport) => - sortedReport?.reportID !== reportID && - sortedReport?.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN && - ReportUtils.shouldReportBeInOptionList({ - report: sortedReport, - currentReportId: '', - isInGSDMode: false, - betas: [], - policies: {}, - excludeEmptyChats: true, - doesReportHaveViolations: false, - }), - ); - const lastAccessedReportID = filteredReportsByLastRead.at(-1)?.reportID; - - if (lastAccessedReportID) { - // We should call Navigation.goBack to pop the current route first before navigating to Concierge. - Navigation.goBack(ROUTES.HOME); - Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(lastAccessedReportID)); - } else { - const participantAccountIDs = PersonalDetailsUtils.getAccountIDsByLogins([CONST.EMAIL.CONCIERGE]); - const chat = ReportUtils.getChatByParticipants(participantAccountIDs); - if (chat?.reportID) { - // We should call Navigation.goBack to pop the current route first before navigating to Concierge. - Navigation.goBack(ROUTES.HOME); - Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(chat.reportID)); - } - } + ReportUtils.navigateUserOnceLeaveReport(reportID); } /** Invites people to a room */ From 95b8741eb659e4e9b83776fdc40545a523585953 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 5 Feb 2024 17:48:58 +0100 Subject: [PATCH 008/600] use real api for leaving workspaces --- .../LeavePolicyExpenseChatParams.ts | 6 - .../API/parameters/LeaveWorkspaceParams.ts | 6 - src/libs/API/parameters/index.ts | 2 - src/libs/API/types.ts | 5 - src/libs/actions/Policy.ts | 126 ++++++------------ src/pages/home/HeaderView.js | 4 +- src/pages/workspace/WorkspacesListPage.tsx | 3 +- 7 files changed, 46 insertions(+), 106 deletions(-) delete mode 100644 src/libs/API/parameters/LeavePolicyExpenseChatParams.ts delete mode 100644 src/libs/API/parameters/LeaveWorkspaceParams.ts diff --git a/src/libs/API/parameters/LeavePolicyExpenseChatParams.ts b/src/libs/API/parameters/LeavePolicyExpenseChatParams.ts deleted file mode 100644 index 665b28b9e0a2..000000000000 --- a/src/libs/API/parameters/LeavePolicyExpenseChatParams.ts +++ /dev/null @@ -1,6 +0,0 @@ -type LeavePolicyExpenseChatParams = { - // TODO: Clarify - reportID: string; -}; - -export default LeavePolicyExpenseChatParams; diff --git a/src/libs/API/parameters/LeaveWorkspaceParams.ts b/src/libs/API/parameters/LeaveWorkspaceParams.ts deleted file mode 100644 index b28fa3e5aed9..000000000000 --- a/src/libs/API/parameters/LeaveWorkspaceParams.ts +++ /dev/null @@ -1,6 +0,0 @@ -type LeaveWorkspaceParams = { - // TODO: Clarify - policyID: string; -}; - -export default LeaveWorkspaceParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index a41227a60507..b7c3dff7c342 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -90,9 +90,7 @@ export type {default as AddWorkspaceRoomParams} from './AddWorkspaceRoomParams'; export type {default as UpdatePolicyRoomNameParams} from './UpdatePolicyRoomNameParams'; export type {default as AddEmojiReactionParams} from './AddEmojiReactionParams'; export type {default as RemoveEmojiReactionParams} from './RemoveEmojiReactionParams'; -export type {default as LeavePolicyExpenseChatParams} from './LeavePolicyExpenseChatParams'; export type {default as LeaveRoomParams} from './LeaveRoomParams'; -export type {default as LeaveWorkspaceParams} from './LeaveWorkspaceParams'; export type {default as InviteToRoomParams} from './InviteToRoomParams'; export type {default as RemoveFromRoomParams} from './RemoveFromRoomParams'; export type {default as FlagCommentParams} from './FlagCommentParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index aa1f601ed575..c011fa395f0f 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -116,9 +116,6 @@ const WRITE_COMMANDS = { SET_NAME_VALUE_PAIR: 'SetNameValuePair', SET_REPORT_FIELD: 'Report_SetFields', SET_REPORT_NAME: 'RenameReport', - // TODO: Clarify - LEAVE_WORKSPACE: 'LeaveWorkspace', - LEAVE_POLICY_EXPENSE_CHAT: 'LeaveWorkspaceExpenseChat', } as const; type WriteCommand = ValueOf; @@ -232,8 +229,6 @@ type WriteCommandParameters = { [WRITE_COMMANDS.SET_NAME_VALUE_PAIR]: Parameters.SetNameValuePairParams; [WRITE_COMMANDS.SET_REPORT_FIELD]: Parameters.SetReportFieldParams; [WRITE_COMMANDS.SET_REPORT_NAME]: Parameters.SetReportNameParams; - [WRITE_COMMANDS.LEAVE_WORKSPACE]: Parameters.LeaveWorkspaceParams; - [WRITE_COMMANDS.LEAVE_POLICY_EXPENSE_CHAT]: Parameters.LeavePolicyExpenseChatParams; }; const READ_COMMANDS = { diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 929318ed9856..d718fcd1b88a 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -15,8 +15,6 @@ import type { DeleteMembersFromWorkspaceParams, DeleteWorkspaceAvatarParams, DeleteWorkspaceParams, - LeavePolicyExpenseChatParams, - LeaveWorkspaceParams, OpenDraftWorkspaceRequestParams, OpenWorkspaceInvitePageParams, OpenWorkspaceMembersPageParams, @@ -41,6 +39,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type {PersonalDetailsList, Policy, PolicyMember, PolicyTags, RecentlyUsedCategories, RecentlyUsedTags, ReimbursementAccount, Report, ReportAction, Transaction} from '@src/types/onyx'; import type {Errors} from '@src/types/onyx/OnyxCommon'; import type {CustomUnit} from '@src/types/onyx/Policy'; +import type {EmptyObject} from '@src/types/utils/EmptyObject'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; type AnnounceRoomMembersOnyxData = { @@ -354,8 +353,8 @@ function buildAnnounceRoomMembersOnyxData(policyID: string, accountIDs: number[] /** * Build optimistic data for removing users from the announcement room */ -function removeOptimisticAnnounceRoomMembers(policyID: string, accountIDs: number[]): AnnounceRoomMembersOnyxData { - const announceReport = ReportUtils.getRoom(CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE, policyID); +function removeOptimisticAnnounceRoomMembers(policy: Policy | EmptyObject, accountIDs: number[]): AnnounceRoomMembersOnyxData { + const announceReport = ReportUtils.getRoom(CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE, policy.id); const announceRoomMembers: AnnounceRoomMembersOnyxData = { onyxOptimisticData: [], onyxFailureData: [], @@ -367,21 +366,37 @@ function removeOptimisticAnnounceRoomMembers(policyID: string, accountIDs: numbe if (announceReport?.participantAccountIDs) { const remainUsers = announceReport.participantAccountIDs.filter((e) => !accountIDs.includes(e)); + announceRoomMembers.onyxOptimisticData.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${announceReport.reportID}`, value: { participantAccountIDs: [...remainUsers], visibleChatMemberAccountIDs: [...remainUsers], + ...(accountIDs.includes(sessionAccountID) + ? { + statusNum: CONST.REPORT.STATUS_NUM.CLOSED, + stateNum: CONST.REPORT.STATE_NUM.APPROVED, + oldPolicyName: policy.name, + hasDraft: false, + } + : {}), }, }); - announceRoomMembers.onyxFailureData.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${announceReport.reportID}`, value: { participantAccountIDs: announceReport.participantAccountIDs, visibleChatMemberAccountIDs: announceReport.visibleChatMemberAccountIDs, + ...(accountIDs.includes(sessionAccountID) + ? { + statusNum: announceReport.statusNum, + stateNum: announceReport.stateNum, + oldPolicyName: announceReport.oldPolicyName, + hasDraft: announceReport.hasDraft, + } + : {}), }, }); } @@ -404,7 +419,7 @@ function removeMembers(accountIDs: number[], policyID: string) { const workspaceChats = ReportUtils.getWorkspaceChats(policyID, accountIDs); const optimisticClosedReportActions = workspaceChats.map(() => ReportUtils.buildOptimisticClosedReportAction(sessionEmail, policy.name, CONST.REPORT.ARCHIVE_REASON.REMOVED_FROM_POLICY)); - const announceRoomMembers = removeOptimisticAnnounceRoomMembers(policyID, accountIDs); + const announceRoomMembers = removeOptimisticAnnounceRoomMembers(policy, accountIDs); const optimisticMembersState: OnyxCollection = {}; const successMembersState: OnyxCollection = {}; @@ -507,11 +522,34 @@ function removeMembers(accountIDs: number[], policyID: string) { }); }); + if (accountIDs.includes(sessionAccountID)) { + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + }, + }); + failureData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + pendingAction: policy.pendingAction, + }, + }); + } + const params: DeleteMembersFromWorkspaceParams = { emailList: accountIDs.map((accountID) => allPersonalDetails?.[accountID]?.login).join(','), policyID, }; + console.log('WRITE_COMMANDS.DELETE_MEMBERS_FROM_WORKSPACE, params, {optimisticData, successData, failureData}', WRITE_COMMANDS.DELETE_MEMBERS_FROM_WORKSPACE, params, { + optimisticData, + successData, + failureData, + }); + API.write(WRITE_COMMANDS.DELETE_MEMBERS_FROM_WORKSPACE, params, {optimisticData, successData, failureData}); } @@ -1987,80 +2025,6 @@ function createWorkspaceFromIOUPayment(iouReport: Report): string | undefined { return policyID; } -/** - * TODO: Comment - */ -function leaveWorkspace(policyID: string) { - const optimisticData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - value: { - avatar: '', - pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, - errors: null, - }, - }, - ]; - const parameters: LeaveWorkspaceParams = { - policyID, - }; - - console.group('leaveWorkspace'); - console.log('policyID', policyID); - console.log('{parameters, optimisticData}', {parameters, optimisticData}); - console.groupEnd(); - - API.write(WRITE_COMMANDS.LEAVE_WORKSPACE, parameters, {optimisticData}); -} - -/** - * TODO: Comment - */ -function leavePolicyExpenseChat(reportID: string) { - const report = ReportUtils.getReport(reportID); - - const optimisticData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, - value: { - notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, - }, - }, - ]; - const successData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, - value: { - notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, - }, - }, - ]; - const failureData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, - value: report, - }, - ]; - - const parameters: LeavePolicyExpenseChatParams = { - reportID, - }; - - console.group('leaveWorkspace'); - console.log('reportID', reportID); - console.log('parameters', parameters); - console.log('{optimisticData, successData, failureData}', {optimisticData, successData, failureData}); - console.groupEnd(); - - API.write(WRITE_COMMANDS.LEAVE_POLICY_EXPENSE_CHAT, parameters, {optimisticData, successData, failureData}); - - ReportUtils.navigateUserOnceLeaveReport(reportID); -} - export { removeMembers, addMembersToWorkspace, @@ -2097,6 +2061,4 @@ export { buildOptimisticPolicyRecentlyUsedTags, createDraftInitialWorkspace, setWorkspaceInviteMessageDraft, - leaveWorkspace, - leavePolicyExpenseChat, }; diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js index e957c70ed5cc..9cd78b47271b 100644 --- a/src/pages/home/HeaderView.js +++ b/src/pages/home/HeaderView.js @@ -34,7 +34,6 @@ import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import reportPropTypes from '@pages/reportPropTypes'; import * as Link from '@userActions/Link'; -import * as Policy from '@userActions/Policy'; import * as Report from '@userActions/Report'; import * as Session from '@userActions/Session'; import * as Task from '@userActions/Task'; @@ -164,11 +163,10 @@ function HeaderView(props) { }); } else if (canLeave) { const isWorkspaceMemberLeavingWorkspaceRoom = !isChatThread && lodashGet(props.report, 'visibility', '') === CONST.REPORT.VISIBILITY.RESTRICTED && isPolicyMember; - const action = isPolicyExpenseChat ? () => Policy.leavePolicyExpenseChat(props.reportID) : () => Report.leaveRoom(props.reportID, isWorkspaceMemberLeavingWorkspaceRoom); threeDotMenuItems.push({ icon: Expensicons.ChatBubbles, text: translate('common.leave'), - onSelected: Session.checkIfActionIsAllowed(action), + onSelected: Session.checkIfActionIsAllowed(() => Report.leaveRoom(props.reportID, isWorkspaceMemberLeavingWorkspaceRoom)), }); } diff --git a/src/pages/workspace/WorkspacesListPage.tsx b/src/pages/workspace/WorkspacesListPage.tsx index c8e518429557..bda81eee0011 100755 --- a/src/pages/workspace/WorkspacesListPage.tsx +++ b/src/pages/workspace/WorkspacesListPage.tsx @@ -177,8 +177,7 @@ function WorkspacesListPage({policies, allPolicyMembers, reimbursementAccount, r threeDotsMenuItems.push({ icon: Expensicons.ChatBubbles, text: translate('common.leave'), - // TODO: Integrate a handler - onSelected: Session.checkIfActionIsAllowed(() => Policy.leaveWorkspace(item.policyID ?? '')), + onSelected: Session.checkIfActionIsAllowed(() => Policy.removeMembers([session?.accountID ?? 0], item.policyID ?? '')), }); } From af8c733a9e824b07f3accc4131994fc4a0f579fe Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 5 Feb 2024 17:51:55 +0100 Subject: [PATCH 009/600] Revert "integrate draft api data" This reverts commit 3775ad084b4ed838a7b4ad32793c2d9568ecc9e1. --- .../AppNavigator/ReportScreenIDSetter.ts | 26 ++++++++-- src/libs/ReportUtils.ts | 48 ++----------------- src/libs/actions/Report.ts | 42 +++++++++++++++- 3 files changed, 65 insertions(+), 51 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts b/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts index 927e4c57d66b..b4bb56262860 100644 --- a/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts +++ b/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts @@ -7,7 +7,7 @@ import {getPolicyMembersByIdWithoutCurrentUser} from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as App from '@userActions/App'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Policy, PolicyMembers, Report} from '@src/types/onyx'; +import type {Policy, PolicyMembers, Report, ReportMetadata} from '@src/types/onyx'; import type {ReportScreenWrapperProps} from './ReportScreenWrapper'; type ReportScreenIDSetterComponentProps = { @@ -23,6 +23,9 @@ type ReportScreenIDSetterComponentProps = { /** Whether user is a new user */ isFirstTimeNewExpensifyUser: OnyxEntry; + /** The report metadata */ + reportMetadata: OnyxCollection; + /** The accountID of the current user */ accountID?: number; }; @@ -38,15 +41,25 @@ const getLastAccessedReportID = ( policies: OnyxCollection, isFirstTimeNewExpensifyUser: OnyxEntry, openOnAdminRoom: boolean, + reportMetadata: OnyxCollection, policyID?: string, policyMemberAccountIDs?: number[], ): string | undefined => { - const lastReport = ReportUtils.findLastAccessedReport(reports, ignoreDefaultRooms, policies, !!isFirstTimeNewExpensifyUser, openOnAdminRoom, policyID, policyMemberAccountIDs); + const lastReport = ReportUtils.findLastAccessedReport( + reports, + ignoreDefaultRooms, + policies, + !!isFirstTimeNewExpensifyUser, + openOnAdminRoom, + reportMetadata, + policyID, + policyMemberAccountIDs, + ); return lastReport?.reportID; }; // This wrapper is reponsible for opening the last accessed report if there is no reportID specified in the route params -function ReportScreenIDSetter({route, reports, policies, policyMembers = {}, navigation, isFirstTimeNewExpensifyUser = false, accountID}: ReportScreenIDSetterProps) { +function ReportScreenIDSetter({route, reports, policies, policyMembers = {}, navigation, isFirstTimeNewExpensifyUser = false, reportMetadata, accountID}: ReportScreenIDSetterProps) { const {canUseDefaultRooms} = usePermissions(); const {activeWorkspaceID} = useActiveWorkspace(); @@ -71,6 +84,7 @@ function ReportScreenIDSetter({route, reports, policies, policyMembers = {}, nav policies, isFirstTimeNewExpensifyUser, !!reports?.params?.openOnAdminRoom, + reportMetadata, activeWorkspaceID, policyMemberAccountIDs, ); @@ -82,7 +96,7 @@ function ReportScreenIDSetter({route, reports, policies, policyMembers = {}, nav } else { App.confirmReadyToOpenApp(); } - }, [route, navigation, reports, canUseDefaultRooms, policies, isFirstTimeNewExpensifyUser, activeWorkspaceID, policyMembers, accountID]); + }, [route, navigation, reports, canUseDefaultRooms, policies, isFirstTimeNewExpensifyUser, reportMetadata, activeWorkspaceID, policyMembers, accountID]); // The ReportScreen without the reportID set will display a skeleton // until the reportID is loaded and set in the route param @@ -108,6 +122,10 @@ export default withOnyx session?.accountID, diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 045ed1d6a1e2..7594d9db0fdd 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -503,13 +503,6 @@ Onyx.connect({ }, }); -let reportMetadata: OnyxCollection = {}; -Onyx.connect({ - key: ONYXKEYS.COLLECTION.REPORT_METADATA, - waitForCollectionCallback: true, - callback: (value) => (reportMetadata = value), -}); - function getChatType(report: OnyxEntry): ValueOf | undefined { return report?.chatType; } @@ -930,7 +923,7 @@ function filterReportsByPolicyIDAndMemberAccountIDs(reports: Report[], policyMem /** * Given an array of reports, return them sorted by the last read timestamp. */ -function sortReportsByLastRead(reports: Report[]): Array> { +function sortReportsByLastRead(reports: Report[], reportMetadata: OnyxCollection): Array> { return reports .filter((report) => !!report?.reportID && !!(reportMetadata?.[`${ONYXKEYS.COLLECTION.REPORT_METADATA}${report.reportID}`]?.lastVisitTime ?? report?.lastReadTime)) .sort((a, b) => { @@ -1010,6 +1003,7 @@ function findLastAccessedReport( policies: OnyxCollection, isFirstTimeNewExpensifyUser: boolean, openOnAdminRoom = false, + reportMetadata: OnyxCollection = {}, policyID?: string, policyMemberAccountIDs: number[] = [], ): OnyxEntry { @@ -1025,7 +1019,7 @@ function findLastAccessedReport( reportsValues = filterReportsByPolicyIDAndMemberAccountIDs(reportsValues, policyMemberAccountIDs, policyID); } - let sortedReports = sortReportsByLastRead(reportsValues); + let sortedReports = sortReportsByLastRead(reportsValues, reportMetadata); let adminReport: OnyxEntry | undefined; if (openOnAdminRoom) { @@ -4771,41 +4765,6 @@ function isReportOwner(report: OnyxEntry): boolean { return report?.ownerAccountID === currentUserPersonalDetails?.accountID; } -function navigateUserOnceLeaveReport(reportID: string) { - const sortedReportsByLastRead = sortReportsByLastRead(Object.values(allReports ?? {}) as Report[]); - - // We want to filter out the current report, hidden reports and empty chats - const filteredReportsByLastRead = sortedReportsByLastRead.filter( - (sortedReport) => - sortedReport?.reportID !== reportID && - sortedReport?.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN && - shouldReportBeInOptionList({ - report: sortedReport, - currentReportId: '', - isInGSDMode: false, - betas: [], - policies: {}, - excludeEmptyChats: true, - doesReportHaveViolations: false, - }), - ); - const lastAccessedReportID = filteredReportsByLastRead.at(-1)?.reportID; - - if (lastAccessedReportID) { - // We should call Navigation.goBack to pop the current route first before navigating to Concierge. - Navigation.goBack(ROUTES.HOME); - Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(lastAccessedReportID)); - } else { - const participantAccountIDs = PersonalDetailsUtils.getAccountIDsByLogins([CONST.EMAIL.CONCIERGE]); - const chat = getChatByParticipants(participantAccountIDs); - if (chat?.reportID) { - // We should call Navigation.goBack to pop the current route first before navigating to Concierge. - Navigation.goBack(ROUTES.HOME); - Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(chat.reportID)); - } - } -} - export { getReportParticipantsTitle, isReportMessageAttachment, @@ -4994,7 +4953,6 @@ export { isReportFieldDisabled, getAvailableReportFields, isReportOwner, - navigateUserOnceLeaveReport, getAllAncestorReportActionIDs, }; diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index c07b330b5a68..4bff826ceb3a 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -66,7 +66,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; import ROUTES from '@src/ROUTES'; -import type {PersonalDetails, PersonalDetailsList, PolicyReportField, RecentlyUsedReportFields, ReportActionReactions, ReportUserIsTyping} from '@src/types/onyx'; +import type {PersonalDetails, PersonalDetailsList, PolicyReportField, RecentlyUsedReportFields, ReportActionReactions, ReportMetadata, ReportUserIsTyping} from '@src/types/onyx'; import type {Decision, OriginalMessageIOU} from '@src/types/onyx/OriginalMessage'; import type {NotificationPreference, WriteCapability} from '@src/types/onyx/Report'; import type Report from '@src/types/onyx/Report'; @@ -159,6 +159,13 @@ Onyx.connect({ }, }); +let reportMetadata: OnyxCollection = {}; +Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT_METADATA, + waitForCollectionCallback: true, + callback: (value) => (reportMetadata = value), +}); + const allReports: OnyxCollection = {}; let conciergeChatReportID: string | undefined; const typingWatchTimers: Record = {}; @@ -2241,7 +2248,38 @@ function leaveRoom(reportID: string, isWorkspaceMemberLeavingWorkspaceRoom = fal API.write(WRITE_COMMANDS.LEAVE_ROOM, parameters, {optimisticData, successData, failureData}); - ReportUtils.navigateUserOnceLeaveReport(reportID); + const sortedReportsByLastRead = ReportUtils.sortReportsByLastRead(Object.values(allReports ?? {}) as Report[], reportMetadata); + + // We want to filter out the current report, hidden reports and empty chats + const filteredReportsByLastRead = sortedReportsByLastRead.filter( + (sortedReport) => + sortedReport?.reportID !== reportID && + sortedReport?.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN && + ReportUtils.shouldReportBeInOptionList({ + report: sortedReport, + currentReportId: '', + isInGSDMode: false, + betas: [], + policies: {}, + excludeEmptyChats: true, + doesReportHaveViolations: false, + }), + ); + const lastAccessedReportID = filteredReportsByLastRead.at(-1)?.reportID; + + if (lastAccessedReportID) { + // We should call Navigation.goBack to pop the current route first before navigating to Concierge. + Navigation.goBack(ROUTES.HOME); + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(lastAccessedReportID)); + } else { + const participantAccountIDs = PersonalDetailsUtils.getAccountIDsByLogins([CONST.EMAIL.CONCIERGE]); + const chat = ReportUtils.getChatByParticipants(participantAccountIDs); + if (chat?.reportID) { + // We should call Navigation.goBack to pop the current route first before navigating to Concierge. + Navigation.goBack(ROUTES.HOME); + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(chat.reportID)); + } + } } /** Invites people to a room */ From 8a3473f35c8ed37cef7ab45dbeed45467d32c04e Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Mon, 5 Feb 2024 17:54:11 +0100 Subject: [PATCH 010/600] minor fix --- src/libs/actions/Policy.ts | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index d718fcd1b88a..d012fba90ef0 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ import {PUBLIC_DOMAINS} from 'expensify-common/lib/CONST'; import ExpensiMark from 'expensify-common/lib/ExpensiMark'; import Str from 'expensify-common/lib/str'; @@ -39,7 +38,6 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type {PersonalDetailsList, Policy, PolicyMember, PolicyTags, RecentlyUsedCategories, RecentlyUsedTags, ReimbursementAccount, Report, ReportAction, Transaction} from '@src/types/onyx'; import type {Errors} from '@src/types/onyx/OnyxCommon'; import type {CustomUnit} from '@src/types/onyx/Policy'; -import type {EmptyObject} from '@src/types/utils/EmptyObject'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; type AnnounceRoomMembersOnyxData = { @@ -353,8 +351,8 @@ function buildAnnounceRoomMembersOnyxData(policyID: string, accountIDs: number[] /** * Build optimistic data for removing users from the announcement room */ -function removeOptimisticAnnounceRoomMembers(policy: Policy | EmptyObject, accountIDs: number[]): AnnounceRoomMembersOnyxData { - const announceReport = ReportUtils.getRoom(CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE, policy.id); +function removeOptimisticAnnounceRoomMembers(policyID: string, policyName: string, accountIDs: number[]): AnnounceRoomMembersOnyxData { + const announceReport = ReportUtils.getRoom(CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE, policyID); const announceRoomMembers: AnnounceRoomMembersOnyxData = { onyxOptimisticData: [], onyxFailureData: [], @@ -377,7 +375,7 @@ function removeOptimisticAnnounceRoomMembers(policy: Policy | EmptyObject, accou ? { statusNum: CONST.REPORT.STATUS_NUM.CLOSED, stateNum: CONST.REPORT.STATE_NUM.APPROVED, - oldPolicyName: policy.name, + oldPolicyName: policyName, hasDraft: false, } : {}), @@ -419,7 +417,7 @@ function removeMembers(accountIDs: number[], policyID: string) { const workspaceChats = ReportUtils.getWorkspaceChats(policyID, accountIDs); const optimisticClosedReportActions = workspaceChats.map(() => ReportUtils.buildOptimisticClosedReportAction(sessionEmail, policy.name, CONST.REPORT.ARCHIVE_REASON.REMOVED_FROM_POLICY)); - const announceRoomMembers = removeOptimisticAnnounceRoomMembers(policy, accountIDs); + const announceRoomMembers = removeOptimisticAnnounceRoomMembers(policy.id, policy.name, accountIDs); const optimisticMembersState: OnyxCollection = {}; const successMembersState: OnyxCollection = {}; @@ -544,12 +542,6 @@ function removeMembers(accountIDs: number[], policyID: string) { policyID, }; - console.log('WRITE_COMMANDS.DELETE_MEMBERS_FROM_WORKSPACE, params, {optimisticData, successData, failureData}', WRITE_COMMANDS.DELETE_MEMBERS_FROM_WORKSPACE, params, { - optimisticData, - successData, - failureData, - }); - API.write(WRITE_COMMANDS.DELETE_MEMBERS_FROM_WORKSPACE, params, {optimisticData, successData, failureData}); } From 0ce1db13f56356cba7434de0603a1e0601c7b156 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Tue, 6 Feb 2024 15:28:32 +0100 Subject: [PATCH 011/600] move button to top --- src/pages/workspace/WorkspacesListPage.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/pages/workspace/WorkspacesListPage.tsx b/src/pages/workspace/WorkspacesListPage.tsx index bda81eee0011..b51e14adbe9e 100755 --- a/src/pages/workspace/WorkspacesListPage.tsx +++ b/src/pages/workspace/WorkspacesListPage.tsx @@ -157,6 +157,14 @@ function WorkspacesListPage({policies, allPolicyMembers, reimbursementAccount, r }); } + if (!(isAdmin || isOwner)) { + threeDotsMenuItems.push({ + icon: Expensicons.ChatBubbles, + text: translate('common.leave'), + onSelected: Session.checkIfActionIsAllowed(() => Policy.removeMembers([session?.accountID ?? 0], item.policyID ?? '')), + }); + } + if (isAdmin && item.adminRoom) { threeDotsMenuItems.push({ icon: Expensicons.Hashtag, @@ -173,14 +181,6 @@ function WorkspacesListPage({policies, allPolicyMembers, reimbursementAccount, r }); } - if (!(isAdmin || isOwner)) { - threeDotsMenuItems.push({ - icon: Expensicons.ChatBubbles, - text: translate('common.leave'), - onSelected: Session.checkIfActionIsAllowed(() => Policy.removeMembers([session?.accountID ?? 0], item.policyID ?? '')), - }); - } - return ( Date: Mon, 12 Feb 2024 14:12:35 +0530 Subject: [PATCH 012/600] Migrate to Typescript --- ...eiptPage.js => EditRequestReceiptPage.tsx} | 23 ++-- .../{ReceiptDropUI.js => ReceiptDropUI.tsx} | 23 ++-- .../{index.android.js => index.android.tsx} | 0 .../{index.ios.js => index.ios.tsx} | 0 .../CameraPermission/{index.js => index.tsx} | 0 .../IOURequestStepScanProps.tsx | 9 ++ .../{index.native.js => index.native.tsx} | 14 +-- .../{index.js => index.tsx} | 37 ++----- .../{index.native.js => index.native.tsx} | 88 ++++++--------- .../{index.js => index.tsx} | 103 +++++++----------- 10 files changed, 118 insertions(+), 179 deletions(-) rename src/pages/{EditRequestReceiptPage.js => EditRequestReceiptPage.tsx} (75%) rename src/pages/iou/{ReceiptDropUI.js => ReceiptDropUI.tsx} (73%) rename src/pages/iou/request/step/IOURequestStepScan/CameraPermission/{index.android.js => index.android.tsx} (100%) rename src/pages/iou/request/step/IOURequestStepScan/CameraPermission/{index.ios.js => index.ios.tsx} (100%) rename src/pages/iou/request/step/IOURequestStepScan/CameraPermission/{index.js => index.tsx} (100%) create mode 100644 src/pages/iou/request/step/IOURequestStepScan/IOURequestStepScanProps.tsx rename src/pages/iou/request/step/IOURequestStepScan/NavigationAwareCamera/{index.native.js => index.native.tsx} (66%) rename src/pages/iou/request/step/IOURequestStepScan/NavigationAwareCamera/{index.js => index.tsx} (56%) rename src/pages/iou/request/step/IOURequestStepScan/{index.native.js => index.native.tsx} (84%) rename src/pages/iou/request/step/IOURequestStepScan/{index.js => index.tsx} (82%) diff --git a/src/pages/EditRequestReceiptPage.js b/src/pages/EditRequestReceiptPage.tsx similarity index 75% rename from src/pages/EditRequestReceiptPage.js rename to src/pages/EditRequestReceiptPage.tsx index 40fe64da7eed..3458b8577206 100644 --- a/src/pages/EditRequestReceiptPage.js +++ b/src/pages/EditRequestReceiptPage.tsx @@ -1,4 +1,3 @@ -import PropTypes from 'prop-types'; import React, {useState} from 'react'; import {View} from 'react-native'; import DragAndDropProvider from '@components/DragAndDrop/Provider'; @@ -7,23 +6,16 @@ import ScreenWrapper from '@components/ScreenWrapper'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; +import type {StackScreenProps} from '@react-navigation/stack'; +import type SCREENS from '@src/SCREENS'; +import type {MoneyRequestNavigatorParamList} from '@libs/Navigation/types'; import IOURequestStepScan from './iou/request/step/IOURequestStepScan'; -const propTypes = { - /** React Navigation route */ - route: PropTypes.shape({ - /** Params from the route */ - params: PropTypes.shape({ - /** The type of IOU report, i.e. bill, request, send */ - iouType: PropTypes.string, +type EditRequestReceiptPageProps = StackScreenProps - /** The report ID of the IOU */ - reportID: PropTypes.string, - }), - }).isRequired, -}; - -function EditRequestReceiptPage({route}) { +function EditRequestReceiptPage({ + route, +}: EditRequestReceiptPageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const [isDraggingOver, setIsDraggingOver] = useState(false); @@ -50,7 +42,6 @@ function EditRequestReceiptPage({route}) { ); } -EditRequestReceiptPage.propTypes = propTypes; EditRequestReceiptPage.displayName = 'EditRequestReceiptPage'; export default EditRequestReceiptPage; diff --git a/src/pages/iou/ReceiptDropUI.js b/src/pages/iou/ReceiptDropUI.tsx similarity index 73% rename from src/pages/iou/ReceiptDropUI.js rename to src/pages/iou/ReceiptDropUI.tsx index 0f7226668a80..043458dad113 100644 --- a/src/pages/iou/ReceiptDropUI.js +++ b/src/pages/iou/ReceiptDropUI.tsx @@ -1,4 +1,3 @@ -import PropTypes from 'prop-types'; import React from 'react'; import {View} from 'react-native'; import ReceiptUpload from '@assets/images/receipt-upload.svg'; @@ -9,19 +8,15 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; -const propTypes = { - /** Callback to execute when a file is dropped. */ - onDrop: PropTypes.func.isRequired, - - /** Pixels the receipt image should be shifted down to match the non-drag view UI */ - receiptImageTopPosition: PropTypes.number, -}; - -const defaultProps = { - receiptImageTopPosition: 0, -}; +type ReceiptDropUIProps = { + onDrop: () => void; + receiptImageTopPosition: number; +} -function ReceiptDropUI({onDrop, receiptImageTopPosition}) { +function ReceiptDropUI({ + onDrop, + receiptImageTopPosition=0, +}: ReceiptDropUIProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); return ( @@ -43,7 +38,5 @@ function ReceiptDropUI({onDrop, receiptImageTopPosition}) { } ReceiptDropUI.displayName = 'ReceiptDropUI'; -ReceiptDropUI.propTypes = propTypes; -ReceiptDropUI.defaultProps = defaultProps; export default ReceiptDropUI; diff --git a/src/pages/iou/request/step/IOURequestStepScan/CameraPermission/index.android.js b/src/pages/iou/request/step/IOURequestStepScan/CameraPermission/index.android.tsx similarity index 100% rename from src/pages/iou/request/step/IOURequestStepScan/CameraPermission/index.android.js rename to src/pages/iou/request/step/IOURequestStepScan/CameraPermission/index.android.tsx diff --git a/src/pages/iou/request/step/IOURequestStepScan/CameraPermission/index.ios.js b/src/pages/iou/request/step/IOURequestStepScan/CameraPermission/index.ios.tsx similarity index 100% rename from src/pages/iou/request/step/IOURequestStepScan/CameraPermission/index.ios.js rename to src/pages/iou/request/step/IOURequestStepScan/CameraPermission/index.ios.tsx diff --git a/src/pages/iou/request/step/IOURequestStepScan/CameraPermission/index.js b/src/pages/iou/request/step/IOURequestStepScan/CameraPermission/index.tsx similarity index 100% rename from src/pages/iou/request/step/IOURequestStepScan/CameraPermission/index.js rename to src/pages/iou/request/step/IOURequestStepScan/CameraPermission/index.tsx diff --git a/src/pages/iou/request/step/IOURequestStepScan/IOURequestStepScanProps.tsx b/src/pages/iou/request/step/IOURequestStepScan/IOURequestStepScanProps.tsx new file mode 100644 index 000000000000..cb30251cc84c --- /dev/null +++ b/src/pages/iou/request/step/IOURequestStepScan/IOURequestStepScanProps.tsx @@ -0,0 +1,9 @@ +import type * as OnyxTypes from '@src/types/onyx'; + +type IOURequestStepPropTypes = { + report: OnyxTypes.Report; + + transaction: OnyxTypes.Transaction; +}; + +export default IOURequestStepPropTypes; diff --git a/src/pages/iou/request/step/IOURequestStepScan/NavigationAwareCamera/index.native.js b/src/pages/iou/request/step/IOURequestStepScan/NavigationAwareCamera/index.native.tsx similarity index 66% rename from src/pages/iou/request/step/IOURequestStepScan/NavigationAwareCamera/index.native.js rename to src/pages/iou/request/step/IOURequestStepScan/NavigationAwareCamera/index.native.tsx index 65c17d3cb7ab..f132fe80d709 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/NavigationAwareCamera/index.native.js +++ b/src/pages/iou/request/step/IOURequestStepScan/NavigationAwareCamera/index.native.tsx @@ -1,15 +1,16 @@ -import PropTypes from 'prop-types'; import React from 'react'; +import type {FC} from 'react'; import {Camera} from 'react-native-vision-camera'; +import type {CameraDevice} from 'react-native-vision-camera'; import useTabNavigatorFocus from '@hooks/useTabNavigatorFocus'; -const propTypes = { - /* The index of the tab that contains this camera */ - cameraTabIndex: PropTypes.number.isRequired, +type NavigationAwareCameraProps = { + cameraTabIndex: number; + device: CameraDevice; }; // Wraps a camera that will only be active when the tab is focused or as soon as it starts to become focused. -const NavigationAwareCamera = React.forwardRef(({cameraTabIndex, ...props}, ref) => { +const NavigationAwareCamera = React.forwardRef(({cameraTabIndex, ...props}: NavigationAwareCameraProps, ref: React.Ref) => { const isCameraActive = useTabNavigatorFocus({tabIndex: cameraTabIndex}); return ( @@ -22,7 +23,6 @@ const NavigationAwareCamera = React.forwardRef(({cameraTabIndex, ...props}, ref) ); }); -NavigationAwareCamera.propTypes = propTypes; -NavigationAwareCamera.displayName = 'NavigationAwareCamera'; +(NavigationAwareCamera as FC).displayName = 'NavigationAwareCamera'; export default NavigationAwareCamera; diff --git a/src/pages/iou/request/step/IOURequestStepScan/NavigationAwareCamera/index.js b/src/pages/iou/request/step/IOURequestStepScan/NavigationAwareCamera/index.tsx similarity index 56% rename from src/pages/iou/request/step/IOURequestStepScan/NavigationAwareCamera/index.js rename to src/pages/iou/request/step/IOURequestStepScan/NavigationAwareCamera/index.tsx index 10b16da13b6e..8f9945243a50 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/NavigationAwareCamera/index.js +++ b/src/pages/iou/request/step/IOURequestStepScan/NavigationAwareCamera/index.tsx @@ -1,37 +1,24 @@ -import PropTypes from 'prop-types'; -import React, {useEffect, useRef} from 'react'; +import React, {forwardRef, useEffect, useRef} from 'react'; +import type {FC} from 'react'; import {View} from 'react-native'; import Webcam from 'react-webcam'; import useTabNavigatorFocus from '@hooks/useTabNavigatorFocus'; -const propTypes = { - /** Flag to turn on/off the torch/flashlight - if available */ - torchOn: PropTypes.bool, - - /** The index of the tab that contains this camera */ - cameraTabIndex: PropTypes.number.isRequired, - - /** Callback function when media stream becomes available - user granted camera permissions and camera starts to work */ - onUserMedia: PropTypes.func, - - /** Callback function passing torch/flashlight capability as bool param of the browser */ - onTorchAvailability: PropTypes.func, -}; - -const defaultProps = { - onUserMedia: undefined, - onTorchAvailability: undefined, - torchOn: false, +type NavigationAwareCameraProps = { + torchOn: boolean; + onTorchAvailability?: (torchAvailable: boolean) => void; + cameraTabIndex: number; + onUserMedia?: (stream: MediaStream) => void; }; // Wraps a camera that will only be active when the tab is focused or as soon as it starts to become focused. -const NavigationAwareCamera = React.forwardRef(({torchOn, onTorchAvailability, cameraTabIndex, ...props}, ref) => { - const trackRef = useRef(null); +const NavigationAwareCamera = forwardRef(({torchOn, onTorchAvailability, cameraTabIndex, ...props}: NavigationAwareCameraProps, ref: React.Ref) => { + const trackRef = useRef(null); const shouldShowCamera = useTabNavigatorFocus({ tabIndex: cameraTabIndex, }); - const handleOnUserMedia = (stream) => { + const handleOnUserMedia = (stream: MediaStream) => { if (props.onUserMedia) { props.onUserMedia(stream); } @@ -73,8 +60,6 @@ const NavigationAwareCamera = React.forwardRef(({torchOn, onTorchAvailability, c ); }); -NavigationAwareCamera.propTypes = propTypes; -NavigationAwareCamera.displayName = 'NavigationAwareCamera'; -NavigationAwareCamera.defaultProps = defaultProps; +(NavigationAwareCamera as FC).displayName = 'NavigationAwareCamera'; export default NavigationAwareCamera; diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.native.js b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx similarity index 84% rename from src/pages/iou/request/step/IOURequestStepScan/index.native.js rename to src/pages/iou/request/step/IOURequestStepScan/index.native.tsx index a1a3ed946967..bd6dc55f946d 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.native.js +++ b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx @@ -1,4 +1,4 @@ -import lodashGet from 'lodash/get'; +import type {StackScreenProps} from '@react-navigation/stack'; import React, {useCallback, useEffect, useRef, useState} from 'react'; import {ActivityIndicator, Alert, AppState, View} from 'react-native'; import {Gesture, GestureDetector} from 'react-native-gesture-handler'; @@ -14,7 +14,6 @@ import * as Expensicons from '@components/Icon/Expensicons'; import ImageSVG from '@components/ImageSVG'; import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; import Text from '@components/Text'; -import transactionPropTypes from '@components/transactionPropTypes'; import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -22,49 +21,32 @@ import compose from '@libs/compose'; import * as FileUtils from '@libs/fileDownload/FileUtils'; import Log from '@libs/Log'; import Navigation from '@libs/Navigation/Navigation'; -import IOURequestStepRoutePropTypes from '@pages/iou/request/step/IOURequestStepRoutePropTypes'; +import type {MoneyRequestNavigatorParamList} from '@libs/Navigation/types'; import StepScreenWrapper from '@pages/iou/request/step/StepScreenWrapper'; import withFullTransactionOrNotFound from '@pages/iou/request/step/withFullTransactionOrNotFound'; import withWritableReportOrNotFound from '@pages/iou/request/step/withWritableReportOrNotFound'; -import reportPropTypes from '@pages/reportPropTypes'; import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; import * as CameraPermission from './CameraPermission'; +import type IOURequestStepPropTypes from './IOURequestStepScanProps'; import NavigationAwareCamera from './NavigationAwareCamera'; -const propTypes = { - /** Navigation route context info provided by react navigation */ - route: IOURequestStepRoutePropTypes.isRequired, - - /* Onyx Props */ - /** The report that the transaction belongs to */ - report: reportPropTypes, - - /** The transaction (or draft transaction) being changed */ - transaction: transactionPropTypes, -}; - -const defaultProps = { - report: {}, - transaction: {}, -}; - -function IOURequestStepScan({ - report, - route: { - params: {action, iouType, reportID, transactionID, backTo}, - }, - transaction: {isFromGlobalCreate}, -}) { +type IOURequestStepScanProps = IOURequestStepPropTypes & + StackScreenProps & { + isFromGlobalCreate: boolean; + }; + +function IOURequestStepScan({report, route, isFromGlobalCreate}: IOURequestStepScanProps) { const theme = useTheme(); const styles = useThemeStyles(); const devices = useCameraDevices('wide-angle-camera'); const device = devices.back; - const camera = useRef(null); + const camera = useRef(null); const [flash, setFlash] = useState(false); - const [cameraPermissionStatus, setCameraPermissionStatus] = useState(undefined); + const [cameraPermissionStatus, setCameraPermissionStatus] = useState(null); const {translate} = useLocalize(); @@ -127,19 +109,21 @@ function IOURequestStepScan({ }; }, []); - const validateReceipt = (file) => { - const {fileExtension} = FileUtils.splitExtensionFromFileName(lodashGet(file, 'name', '')); - if (!CONST.API_ATTACHMENT_VALIDATIONS.ALLOWED_RECEIPT_EXTENSIONS.includes(fileExtension.toLowerCase())) { + const validateReceipt = (file: File) => { + const {fileExtension} = FileUtils.splitExtensionFromFileName(file?.name ?? ''); + if ( + !CONST.API_ATTACHMENT_VALIDATIONS.ALLOWED_RECEIPT_EXTENSIONS.includes(fileExtension.toLowerCase() as (typeof CONST.API_ATTACHMENT_VALIDATIONS.ALLOWED_RECEIPT_EXTENSIONS)[number]) + ) { Alert.alert(translate('attachmentPicker.wrongFileType'), translate('attachmentPicker.notAllowedExtension')); return false; } - if (lodashGet(file, 'size', 0) > CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE) { + if ((file?.size ?? 0) > CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE) { Alert.alert(translate('attachmentPicker.attachmentTooLarge'), translate('attachmentPicker.sizeExceeded')); return false; } - if (lodashGet(file, 'size', 0) < CONST.API_ATTACHMENT_VALIDATIONS.MIN_SIZE) { + if ((file?.size ?? 0) < CONST.API_ATTACHMENT_VALIDATIONS.MIN_SIZE) { Alert.alert(translate('attachmentPicker.attachmentTooSmall'), translate('attachmentPicker.sizeNotMet')); return false; } @@ -167,44 +151,44 @@ function IOURequestStepScan({ }; const navigateToConfirmationStep = useCallback(() => { - if (backTo) { - Navigation.goBack(backTo); + if (route.params.backTo) { + Navigation.goBack(route.params.backTo); return; } // If the transaction was created from the global create, the person needs to select participants, so take them there. if (isFromGlobalCreate) { - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_PARTICIPANTS.getRoute(iouType, transactionID, reportID)); + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_PARTICIPANTS.getRoute(route.params.iouType, route.params.transactionID, route.params.reportID)); return; } // If the transaction was created from the + menu from the composer inside of a chat, the participants can automatically // be added to the transaction (taken from the chat report participants) and then the person is taken to the confirmation step. - IOU.setMoneyRequestParticipantsFromReport(transactionID, report); - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(iouType, transactionID, reportID)); - }, [iouType, report, reportID, transactionID, isFromGlobalCreate, backTo]); + IOU.setMoneyRequestParticipantsFromReport(route.params.transactionID, report); + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(route.params.iouType, route.params.transactionID, route.params.reportID)); + }, [route.params.iouType, report, route.params.reportID, route.params.transactionID, isFromGlobalCreate, route.params.backTo]); const updateScanAndNavigate = useCallback( - (file, source) => { + (file: File, source: string) => { Navigation.dismissModal(); - IOU.replaceReceipt(transactionID, file, source); + IOU.replaceReceipt(route.params.transactionID, file, source); }, - [transactionID], + [route.params.transactionID], ); /** * Sets the Receipt objects and navigates the user to the next page * @param {Object} file */ - const setReceiptAndNavigate = (file) => { + const setReceiptAndNavigate = (file: File) => { if (!validateReceipt(file)) { return; } // Store the receipt on the transaction object in Onyx - IOU.setMoneyRequestReceipt(transactionID, file.uri, file.name, action !== CONST.IOU.ACTION.EDIT); + IOU.setMoneyRequestReceipt(route.params.transactionID, file.uri, file.name, route.params.action !== CONST.IOU.ACTION.EDIT); - if (action === CONST.IOU.ACTION.EDIT) { + if (route.params.action === CONST.IOU.ACTION.EDIT) { updateScanAndNavigate(file, file.uri); return; } @@ -230,9 +214,9 @@ function IOURequestStepScan({ .then((photo) => { // Store the receipt on the transaction object in Onyx const source = `file://${photo.path}`; - IOU.setMoneyRequestReceipt(transactionID, source, photo.path, action !== CONST.IOU.ACTION.EDIT); + IOU.setMoneyRequestReceipt(route.params.transactionID, source, photo.path, route.params.action !== CONST.IOU.ACTION.EDIT); - if (action === CONST.IOU.ACTION.EDIT) { + if (route.params.action === CONST.IOU.ACTION.EDIT) { FileUtils.readFileAsync(source, photo.path, (file) => { updateScanAndNavigate(file, source); }); @@ -245,7 +229,7 @@ function IOURequestStepScan({ showCameraAlert(); Log.warn('Error taking photo', error); }); - }, [flash, action, translate, transactionID, updateScanAndNavigate, navigateToConfirmationStep]); + }, [flash, route.params.action, translate, route.params.transactionID, updateScanAndNavigate, navigateToConfirmationStep]); // Wait for camera permission status to render if (cameraPermissionStatus == null) { @@ -257,7 +241,7 @@ function IOURequestStepScan({ includeSafeAreaPaddingBottom headerTitle={translate('common.receipt')} onBackButtonPress={navigateBack} - shouldShowWrapper={Boolean(backTo)} + shouldShowWrapper={Boolean(route.params.backTo)} testID={IOURequestStepScan.displayName} > {cameraPermissionStatus !== RESULTS.GRANTED && ( @@ -363,8 +347,6 @@ function IOURequestStepScan({ ); } -IOURequestStepScan.defaultProps = defaultProps; -IOURequestStepScan.propTypes = propTypes; IOURequestStepScan.displayName = 'IOURequestStepScan'; export default compose(withWritableReportOrNotFound, withFullTransactionOrNotFound)(IOURequestStepScan); diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.js b/src/pages/iou/request/step/IOURequestStepScan/index.tsx similarity index 82% rename from src/pages/iou/request/step/IOURequestStepScan/index.js rename to src/pages/iou/request/step/IOURequestStepScan/index.tsx index 7da97c34cc2b..6a221a423818 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.js +++ b/src/pages/iou/request/step/IOURequestStepScan/index.tsx @@ -1,4 +1,4 @@ -import lodashGet from 'lodash/get'; +import type {StackScreenProps} from '@react-navigation/stack'; import React, {useCallback, useContext, useReducer, useRef, useState} from 'react'; import {ActivityIndicator, PanResponder, PixelRatio, View} from 'react-native'; import Hand from '@assets/images/hand.svg'; @@ -13,7 +13,6 @@ import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; import Text from '@components/Text'; -import transactionPropTypes from '@components/transactionPropTypes'; import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -22,41 +21,25 @@ import * as Browser from '@libs/Browser'; import compose from '@libs/compose'; import * as FileUtils from '@libs/fileDownload/FileUtils'; import Navigation from '@libs/Navigation/Navigation'; +import type {MoneyRequestNavigatorParamList} from '@libs/Navigation/types'; import ReceiptDropUI from '@pages/iou/ReceiptDropUI'; -import IOURequestStepRoutePropTypes from '@pages/iou/request/step/IOURequestStepRoutePropTypes'; import StepScreenDragAndDropWrapper from '@pages/iou/request/step/StepScreenDragAndDropWrapper'; import withFullTransactionOrNotFound from '@pages/iou/request/step/withFullTransactionOrNotFound'; import withWritableReportOrNotFound from '@pages/iou/request/step/withWritableReportOrNotFound'; -import reportPropTypes from '@pages/reportPropTypes'; import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; +import type {TranslationPaths} from '@src/languages/types'; import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; +import type IOURequestStepPropTypes from './IOURequestStepScanProps'; import NavigationAwareCamera from './NavigationAwareCamera'; -const propTypes = { - /** Navigation route context info provided by react navigation */ - route: IOURequestStepRoutePropTypes.isRequired, - - /* Onyx Props */ - /** The report that the transaction belongs to */ - report: reportPropTypes, - - /** The transaction (or draft transaction) being changed */ - transaction: transactionPropTypes, -}; - -const defaultProps = { - report: {}, - transaction: {}, -}; - -function IOURequestStepScan({ - report, - route: { - params: {action, iouType, reportID, transactionID, backTo}, - }, - transaction: {isFromGlobalCreate}, -}) { +type IOURequestStepScanProps = IOURequestStepPropTypes & + StackScreenProps & { + isFromGlobalCreate: boolean; + }; + +function IOURequestStepScan({report, route, isFromGlobalCreate}: IOURequestStepScanProps) { const theme = useTheme(); const styles = useThemeStyles(); @@ -81,29 +64,28 @@ function IOURequestStepScan({ /** * Sets the upload receipt error modal content when an invalid receipt is uploaded - * @param {*} isInvalid - * @param {*} title - * @param {*} reason */ - const setUploadReceiptError = (isInvalid, title, reason) => { + const setUploadReceiptError = (isInvalid: boolean, title: string, reason: string) => { setIsAttachmentInvalid(isInvalid); setAttachmentInvalidReasonTitle(title); setAttachmentValidReason(reason); }; - function validateReceipt(file) { - const {fileExtension} = FileUtils.splitExtensionFromFileName(lodashGet(file, 'name', '')); - if (!CONST.API_ATTACHMENT_VALIDATIONS.ALLOWED_RECEIPT_EXTENSIONS.includes(fileExtension.toLowerCase())) { + function validateReceipt(file: File) { + const {fileExtension} = FileUtils.splitExtensionFromFileName(file?.name ?? ''); + if ( + !CONST.API_ATTACHMENT_VALIDATIONS.ALLOWED_RECEIPT_EXTENSIONS.includes(fileExtension.toLowerCase() as (typeof CONST.API_ATTACHMENT_VALIDATIONS.ALLOWED_RECEIPT_EXTENSIONS)[number]) + ) { setUploadReceiptError(true, 'attachmentPicker.wrongFileType', 'attachmentPicker.notAllowedExtension'); return false; } - if (lodashGet(file, 'size', 0) > CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE) { + if ((file?.size ?? 0) > CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE) { setUploadReceiptError(true, 'attachmentPicker.attachmentTooLarge', 'attachmentPicker.sizeExceeded'); return false; } - if (lodashGet(file, 'size', 0) < CONST.API_ATTACHMENT_VALIDATIONS.MIN_SIZE) { + if ((file?.size ?? 0) < CONST.API_ATTACHMENT_VALIDATIONS.MIN_SIZE) { setUploadReceiptError(true, 'attachmentPicker.attachmentTooSmall', 'attachmentPicker.sizeNotMet'); return false; } @@ -112,49 +94,48 @@ function IOURequestStepScan({ } const navigateBack = () => { - Navigation.goBack(backTo); + Navigation.goBack(route.params.backTo); }; const navigateToConfirmationStep = useCallback(() => { - if (backTo) { - Navigation.goBack(backTo); + if (route.params.backTo) { + Navigation.goBack(route.params.backTo); return; } // If the transaction was created from the global create, the person needs to select participants, so take them there. if (isFromGlobalCreate) { - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_PARTICIPANTS.getRoute(iouType, transactionID, reportID)); + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_PARTICIPANTS.getRoute(route.params.iouType, route.params.transactionID, route.params.reportID)); return; } // If the transaction was created from the + menu from the composer inside of a chat, the participants can automatically // be added to the transaction (taken from the chat report participants) and then the person is taken to the confirmation step. - IOU.setMoneyRequestParticipantsFromReport(transactionID, report); - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(iouType, transactionID, reportID)); - }, [iouType, report, reportID, transactionID, isFromGlobalCreate, backTo]); + IOU.setMoneyRequestParticipantsFromReport(route.params.transactionID, report); + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(route.params.iouType, route.params.transactionID, route.params.reportID)); + }, [route.params.iouType, report, route.params.reportID, route.params.transactionID, isFromGlobalCreate, route.params.backTo]); const updateScanAndNavigate = useCallback( - (file, source) => { - IOU.replaceReceipt(transactionID, file, source); + (file: File, source: string) => { + IOU.replaceReceipt(route.params.transactionID, file, source); Navigation.dismissModal(); }, - [transactionID], + [route.params.transactionID], ); /** * Sets the Receipt objects and navigates the user to the next page - * @param {Object} file */ - const setReceiptAndNavigate = (file) => { + const setReceiptAndNavigate = (file: File) => { if (!validateReceipt(file)) { return; } // Store the receipt on the transaction object in Onyx const source = URL.createObjectURL(file); - IOU.setMoneyRequestReceipt(transactionID, source, file.name, action !== CONST.IOU.ACTION.EDIT); + IOU.setMoneyRequestReceipt(route.params.transactionID, source, file.name, route.params.action !== CONST.IOU.ACTION.EDIT); - if (action === CONST.IOU.ACTION.EDIT) { + if (route.params.action === CONST.IOU.ACTION.EDIT) { updateScanAndNavigate(file, source); return; } @@ -163,22 +144,22 @@ function IOURequestStepScan({ }; const capturePhoto = useCallback(() => { - if (!cameraRef.current.getScreenshot) { + if (!cameraRef?.current?.getScreenshot) { return; } - const imageBase64 = cameraRef.current.getScreenshot(); + const imageBase64 = cameraRef?.current?.getScreenshot(); const filename = `receipt_${Date.now()}.png`; const file = FileUtils.base64ToFile(imageBase64, filename); const source = URL.createObjectURL(file); - IOU.setMoneyRequestReceipt(transactionID, source, file.name, action !== CONST.IOU.ACTION.EDIT); + IOU.setMoneyRequestReceipt(route.params.transactionID, source, file.name, route.params.action !== CONST.IOU.ACTION.EDIT); - if (action === CONST.IOU.ACTION.EDIT) { + if (route.params.action === CONST.IOU.ACTION.EDIT) { updateScanAndNavigate(file, source); return; } navigateToConfirmationStep(); - }, [cameraRef, action, transactionID, updateScanAndNavigate, navigateToConfirmationStep]); + }, [cameraRef, route.params.action, route.params.transactionID, updateScanAndNavigate, navigateToConfirmationStep]); const panResponder = useRef( PanResponder.create({ @@ -320,24 +301,24 @@ function IOURequestStepScan({ {!isDraggingOver && (Browser.isMobile() ? mobileCameraView() : desktopUploadView())} { - const file = lodashGet(e, ['dataTransfer', 'files', 0]); + const file = e?.dataTransfer?.files?.[0]; setReceiptAndNavigate(file); }} receiptImageTopPosition={receiptImageTopPosition} /> @@ -346,8 +327,6 @@ function IOURequestStepScan({ ); } -IOURequestStepScan.defaultProps = defaultProps; -IOURequestStepScan.propTypes = propTypes; IOURequestStepScan.displayName = 'IOURequestStepScan'; export default compose(withWritableReportOrNotFound, withFullTransactionOrNotFound)(IOURequestStepScan); From 9a291eb22cb408ac35a2a53c56f7a227b5ff21bf Mon Sep 17 00:00:00 2001 From: codinggeek2023 Date: Mon, 12 Feb 2024 14:20:53 +0530 Subject: [PATCH 013/600] Put Request types --- src/libs/Navigation/types.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index bbdb03ab3df8..667215467160 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -283,6 +283,13 @@ type MoneyRequestNavigatorParamList = { iouType: string; reportID: string; }; + [SCREENS.MONEY_REQUEST.CREATE]: { + action: ValueOf; + iouType: ValueOf; + transactionID: string; + reportID: string; + backTo: Route; + } }; type NewTaskNavigatorParamList = { From 5bcb3e387dd7de6aa2932f65cff629a01069caeb Mon Sep 17 00:00:00 2001 From: codinggeek2023 Date: Mon, 12 Feb 2024 14:38:52 +0530 Subject: [PATCH 014/600] Update types --- .../iou/request/step/IOURequestStepScan/index.native.tsx | 8 ++++---- src/pages/iou/request/step/IOURequestStepScan/index.tsx | 5 +++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx index bd6dc55f946d..597b14f0d8bd 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx @@ -4,7 +4,8 @@ import {ActivityIndicator, Alert, AppState, View} from 'react-native'; import {Gesture, GestureDetector} from 'react-native-gesture-handler'; import {RESULTS} from 'react-native-permissions'; import Animated, {runOnJS, useAnimatedStyle, useSharedValue, withDelay, withSequence, withSpring, withTiming} from 'react-native-reanimated'; -import {useCameraDevices} from 'react-native-vision-camera'; +import { useCameraDevices} from 'react-native-vision-camera'; +import type {Camera, Point} from 'react-native-vision-camera' import Hand from '@assets/images/hand.svg'; import Shutter from '@assets/images/shutter.svg'; import AttachmentPicker from '@components/AttachmentPicker'; @@ -44,7 +45,7 @@ function IOURequestStepScan({report, route, isFromGlobalCreate}: IOURequestStepS const devices = useCameraDevices('wide-angle-camera'); const device = devices.back; - const camera = useRef(null); + const camera = useRef(null); const [flash, setFlash] = useState(false); const [cameraPermissionStatus, setCameraPermissionStatus] = useState(null); @@ -59,7 +60,7 @@ function IOURequestStepScan({report, route, isFromGlobalCreate}: IOURequestStepS transform: [{translateX: focusIndicatorPosition.value.x}, {translateY: focusIndicatorPosition.value.y}, {scale: focusIndicatorScale.value}], })); - const focusCamera = (point) => { + const focusCamera = (point: Point) => { if (!camera.current) { return; } @@ -178,7 +179,6 @@ function IOURequestStepScan({report, route, isFromGlobalCreate}: IOURequestStepS /** * Sets the Receipt objects and navigates the user to the next page - * @param {Object} file */ const setReceiptAndNavigate = (file: File) => { if (!validateReceipt(file)) { diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.tsx index 6a221a423818..4c10218fb925 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.tsx @@ -31,6 +31,7 @@ import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; +import type Webcam from 'react-webcam'; import type IOURequestStepPropTypes from './IOURequestStepScanProps'; import NavigationAwareCamera from './NavigationAwareCamera'; @@ -56,7 +57,7 @@ function IOURequestStepScan({report, route, isFromGlobalCreate}: IOURequestStepS const [cameraPermissionState, setCameraPermissionState] = useState('prompt'); const [isFlashLightOn, toggleFlashlight] = useReducer((state) => !state, false); const [isTorchAvailable, setIsTorchAvailable] = useState(false); - const cameraRef = useRef(null); + const cameraRef = useRef(null) const hideRecieptModal = () => { setIsAttachmentInvalid(false); @@ -149,7 +150,7 @@ function IOURequestStepScan({report, route, isFromGlobalCreate}: IOURequestStepS } const imageBase64 = cameraRef?.current?.getScreenshot(); const filename = `receipt_${Date.now()}.png`; - const file = FileUtils.base64ToFile(imageBase64, filename); + const file = FileUtils.base64ToFile(imageBase64 ?? '', filename); const source = URL.createObjectURL(file); IOU.setMoneyRequestReceipt(route.params.transactionID, source, file.name, route.params.action !== CONST.IOU.ACTION.EDIT); From eb8dd5fd35a2debacc458ad80e421e9e17f9c776 Mon Sep 17 00:00:00 2001 From: codinggeek2023 Date: Mon, 12 Feb 2024 20:03:53 +0530 Subject: [PATCH 015/600] blank commit for HOC --- src/pages/iou/request/step/IOURequestStepScan/index.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.tsx index 4c10218fb925..281844d0567d 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.tsx @@ -280,6 +280,7 @@ function IOURequestStepScan({report, route, isFromGlobalCreate}: IOURequestStepS + {({openPicker}) => (