Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import type CONST from './CONST';
import type {IOUAction, IOUType} from './CONST';
import type {IOURequestType} from './libs/actions/IOU';
import Log from './libs/Log';
import type {RootNavigatorParamList} from './libs/Navigation/types';
import type {ReimbursementAccountStepToOpen} from './libs/ReimbursementAccountUtils';
import type {AttachmentModalScreenParams} from './pages/media/AttachmentModalScreen/types';
import SCREENS from './SCREENS';
import type {Screen} from './SCREENS';
import type {ExitReason} from './types/form/ExitSurveyReasonForm';
Expand Down Expand Up @@ -422,6 +422,10 @@ const ROUTES = {
return `r/${reportID}/avatar` as const;
},
},
ATTACHMENTS: {
route: 'attachment',
getRoute: (params?: ReportAttachmentsRouteParams) => getAttachmentModalScreenRoute('attachment', params),
},
EDIT_CURRENCY_REQUEST: {
route: 'r/:threadReportID/edit/currency',
getRoute: (threadReportID: string, currency: string, backTo: string) => `r/${threadReportID}/edit/currency?currency=${currency}&backTo=${backTo}` as const,
Expand All @@ -447,10 +451,6 @@ const ROUTES = {
return getUrlWithBackToParam(`r/${reportID}/details/shareCode` as const, backTo);
},
},
ATTACHMENTS: {
route: 'attachment',
getRoute: (params?: AttachmentRouteParams) => getAttachmentModalScreenRoute('attachment', params),
},
REPORT_PARTICIPANTS: {
route: 'r/:reportID/participants',
getRoute: (reportID: string, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/participants` as const, backTo),
Expand Down Expand Up @@ -2761,12 +2761,12 @@ const SHARED_ROUTE_PARAMS: Partial<Record<Screen, string[]>> = {
export {getUrlWithBackToParam, PUBLIC_SCREENS_ROUTES, SHARED_ROUTE_PARAMS};
export default ROUTES;

type AttachmentsRoute = typeof ROUTES.ATTACHMENTS.route;
type ReportAddAttachmentRoute = `r/${string}/attachment/add`;
type AttachmentRoutes = AttachmentsRoute | ReportAddAttachmentRoute;
type AttachmentRouteParams = AttachmentModalScreenParams;
type ReportAttachmentsRoute = typeof ROUTES.ATTACHMENTS.route;
type AttachmentRoutes = ReportAttachmentsRoute;

type ReportAttachmentsRouteParams = RootNavigatorParamList[typeof SCREENS.ATTACHMENTS];

function getAttachmentModalScreenRoute(url: AttachmentRoutes, params?: AttachmentRouteParams) {
function getAttachmentModalScreenRoute(url: AttachmentRoutes, params?: ReportAttachmentsRouteParams) {
if (!params?.source) {
return url;
}
Expand Down
103 changes: 64 additions & 39 deletions src/libs/Navigation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ import type {
} from '@react-navigation/native';
import type {TupleToUnion, ValueOf} from 'type-fest';
import type {UpperCaseCharacters} from 'type-fest/source/internal';
import type {FileObject} from '@components/AttachmentComposerModal';
import type {SearchQueryString} from '@components/Search/types';
import type {IOURequestType} from '@libs/actions/IOU';
import type {SaveSearchParams} from '@libs/API/parameters';
import type {ReimbursementAccountStepToOpen} from '@libs/ReimbursementAccountUtils';
import type {AttachmentModalScreenParams} from '@pages/media/AttachmentModalScreen/types';
import type {AvatarSource} from '@libs/UserUtils';
import type {AttachmentModalContainerModalProps} from '@pages/media/AttachmentModalScreen/types';
import type CONST from '@src/CONST';
import type {Country, IOUAction, IOUType} from '@src/CONST';
import type NAVIGATORS from '@src/NAVIGATORS';
Expand Down Expand Up @@ -1869,7 +1871,7 @@ type ReportsSplitNavigatorParamList = {
transactionID?: string;
iouReportID?: string;
};
[SCREENS.ATTACHMENTS]: AttachmentModalScreenParams;
[SCREENS.ATTACHMENTS]: AttachmentModalScreensParamList[typeof SCREENS.ATTACHMENTS];
};

type SettingsSplitNavigatorParamList = {
Expand Down Expand Up @@ -2125,65 +2127,87 @@ type PublicScreensParamList = SharedScreensParamList & {
[NAVIGATORS.TEST_TOOLS_MODAL_NAVIGATOR]: NavigatorScreenParams<TestToolsModalModalNavigatorParamList>;
};

type AuthScreensParamList = SharedScreensParamList & {
[SCREENS.CONCIERGE]: undefined;
[SCREENS.TRACK_EXPENSE]: undefined;
[SCREENS.SUBMIT_EXPENSE]: undefined;
[SCREENS.ATTACHMENTS]: AttachmentModalScreenParams;
[SCREENS.PROFILE_AVATAR]: {
accountID: string;
type AttachmentModalScreensParamList = {
[SCREENS.ATTACHMENTS]: AttachmentModalContainerModalProps & {
source?: AvatarSource;
reportID?: string;
accountID?: number;
attachmentID?: string;
type?: ValueOf<typeof CONST.ATTACHMENT_TYPE>;
fallbackSource?: AvatarSource;
isAuthTokenRequired?: boolean;
originalFileName?: string;
attachmentLink?: string;
headerTitle?: string;
hashKey?: number;
maybeIcon?: boolean;
file?: FileObject;
shouldDisableSendButton?: boolean;
onConfirm?: (file: FileObject) => void;
};
[SCREENS.PROFILE_AVATAR]: AttachmentModalContainerModalProps & {
accountID: number;
backTo?: Routes;
};
[SCREENS.WORKSPACE_AVATAR]: {
[SCREENS.WORKSPACE_AVATAR]: AttachmentModalContainerModalProps & {
policyID: string;
letter?: UpperCaseCharacters;
};
[SCREENS.WORKSPACE_JOIN_USER]: {
policyID: string;
email: string;
};
[SCREENS.REPORT_AVATAR]: {
[SCREENS.REPORT_AVATAR]: AttachmentModalContainerModalProps & {
reportID: string;
policyID?: string;
};
[SCREENS.WORKSPACES_LIST]: {
backTo?: Routes;
};
[SCREENS.NOT_FOUND]: undefined;
[SCREENS.REQUIRE_TWO_FACTOR_AUTH]: undefined;
[NAVIGATORS.REPORTS_SPLIT_NAVIGATOR]: NavigatorScreenParams<ReportsSplitNavigatorParamList>;
[NAVIGATORS.SETTINGS_SPLIT_NAVIGATOR]: NavigatorScreenParams<SettingsSplitNavigatorParamList>;
[NAVIGATORS.WORKSPACE_SPLIT_NAVIGATOR]: NavigatorScreenParams<WorkspaceSplitNavigatorParamList>;
[NAVIGATORS.RIGHT_MODAL_NAVIGATOR]: NavigatorScreenParams<RightModalNavigatorParamList>;
[NAVIGATORS.ONBOARDING_MODAL_NAVIGATOR]: NavigatorScreenParams<OnboardingModalNavigatorParamList>;
[NAVIGATORS.FEATURE_TRAINING_MODAL_NAVIGATOR]: NavigatorScreenParams<FeatureTrainingNavigatorParamList>;
[NAVIGATORS.WELCOME_VIDEO_MODAL_NAVIGATOR]: NavigatorScreenParams<WelcomeVideoModalNavigatorParamList>;
[NAVIGATORS.EXPLANATION_MODAL_NAVIGATOR]: NavigatorScreenParams<ExplanationModalNavigatorParamList>;
[NAVIGATORS.MIGRATED_USER_MODAL_NAVIGATOR]: NavigatorScreenParams<MigratedUserModalNavigatorParamList>;
[NAVIGATORS.TEST_DRIVE_MODAL_NAVIGATOR]: NavigatorScreenParams<TestDriveModalNavigatorParamList>;
[NAVIGATORS.TEST_DRIVE_DEMO_NAVIGATOR]: NavigatorScreenParams<TestDriveDemoNavigatorParamList>;
[NAVIGATORS.SEARCH_FULLSCREEN_NAVIGATOR]: NavigatorScreenParams<SearchFullscreenNavigatorParamList>;
[SCREENS.DESKTOP_SIGN_IN_REDIRECT]: undefined;
[SCREENS.TRANSACTION_RECEIPT]: {
[SCREENS.TRANSACTION_RECEIPT]: AttachmentModalContainerModalProps & {
reportID: string;
transactionID: string;
readonly?: string;
isFromReviewDuplicates?: string;
action?: IOUAction;
iouType?: IOUType;
mergeTransactionID?: string;
};
[SCREENS.MONEY_REQUEST.RECEIPT_PREVIEW]: {
[SCREENS.MONEY_REQUEST.RECEIPT_PREVIEW]: AttachmentModalContainerModalProps & {
reportID: string;
transactionID: string;
action: IOUAction;
iouType: IOUType;
readonly: string;
};
[SCREENS.CONNECTION_COMPLETE]: undefined;
[NAVIGATORS.SHARE_MODAL_NAVIGATOR]: NavigatorScreenParams<ShareNavigatorParamList>;
[SCREENS.BANK_CONNECTION_COMPLETE]: undefined;
[NAVIGATORS.TEST_TOOLS_MODAL_NAVIGATOR]: NavigatorScreenParams<TestToolsModalModalNavigatorParamList>;
};

type AuthScreensParamList = SharedScreensParamList &
AttachmentModalScreensParamList & {
[SCREENS.CONCIERGE]: undefined;
[SCREENS.TRACK_EXPENSE]: undefined;
[SCREENS.SUBMIT_EXPENSE]: undefined;
[SCREENS.WORKSPACES_LIST]: {
backTo?: Routes;
};
[SCREENS.WORKSPACE_JOIN_USER]: {
policyID: string;
email: string;
};
[SCREENS.NOT_FOUND]: undefined;
[SCREENS.REQUIRE_TWO_FACTOR_AUTH]: undefined;
[NAVIGATORS.REPORTS_SPLIT_NAVIGATOR]: NavigatorScreenParams<ReportsSplitNavigatorParamList>;
[NAVIGATORS.SETTINGS_SPLIT_NAVIGATOR]: NavigatorScreenParams<SettingsSplitNavigatorParamList>;
[NAVIGATORS.WORKSPACE_SPLIT_NAVIGATOR]: NavigatorScreenParams<WorkspaceSplitNavigatorParamList>;
[NAVIGATORS.RIGHT_MODAL_NAVIGATOR]: NavigatorScreenParams<RightModalNavigatorParamList>;
[NAVIGATORS.ONBOARDING_MODAL_NAVIGATOR]: NavigatorScreenParams<OnboardingModalNavigatorParamList>;
[NAVIGATORS.FEATURE_TRAINING_MODAL_NAVIGATOR]: NavigatorScreenParams<FeatureTrainingNavigatorParamList>;
[NAVIGATORS.WELCOME_VIDEO_MODAL_NAVIGATOR]: NavigatorScreenParams<WelcomeVideoModalNavigatorParamList>;
[NAVIGATORS.EXPLANATION_MODAL_NAVIGATOR]: NavigatorScreenParams<ExplanationModalNavigatorParamList>;
[NAVIGATORS.MIGRATED_USER_MODAL_NAVIGATOR]: NavigatorScreenParams<MigratedUserModalNavigatorParamList>;
[NAVIGATORS.TEST_DRIVE_MODAL_NAVIGATOR]: NavigatorScreenParams<TestDriveModalNavigatorParamList>;
[NAVIGATORS.TEST_DRIVE_DEMO_NAVIGATOR]: NavigatorScreenParams<TestDriveDemoNavigatorParamList>;
[NAVIGATORS.SEARCH_FULLSCREEN_NAVIGATOR]: NavigatorScreenParams<SearchFullscreenNavigatorParamList>;
[SCREENS.DESKTOP_SIGN_IN_REDIRECT]: undefined;
[SCREENS.CONNECTION_COMPLETE]: undefined;
[NAVIGATORS.SHARE_MODAL_NAVIGATOR]: NavigatorScreenParams<ShareNavigatorParamList>;
[SCREENS.BANK_CONNECTION_COMPLETE]: undefined;
[NAVIGATORS.TEST_TOOLS_MODAL_NAVIGATOR]: NavigatorScreenParams<TestToolsModalModalNavigatorParamList>;
};

type SearchReportParamList = {
[SCREENS.SEARCH.REPORT_RHP]: {
reportID: string;
Expand Down Expand Up @@ -2420,4 +2444,5 @@ export type {
ReportChangeApproverParamList,
TestToolsModalModalNavigatorParamList,
MergeTransactionNavigatorParamList,
AttachmentModalScreensParamList,
};
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import type {RefObject} from 'react';
import React, {memo, useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {InteractionManager, Keyboard, View} from 'react-native';
import {GestureHandlerRootView} from 'react-native-gesture-handler';
import type {OnyxEntry} from 'react-native-onyx';
import Animated, {FadeIn, LayoutAnimationConfig, useSharedValue} from 'react-native-reanimated';
import type {ValueOf} from 'type-fest';
import AttachmentCarousel from '@components/Attachments/AttachmentCarousel';
import AttachmentCarouselPagerContext from '@components/Attachments/AttachmentCarousel/Pager/AttachmentCarouselPagerContext';
import AttachmentView from '@components/Attachments/AttachmentView';
Expand Down Expand Up @@ -33,140 +30,15 @@ import Navigation from '@libs/Navigation/Navigation';
import {getOriginalMessage, getReportAction, isMoneyRequestAction} from '@libs/ReportActionsUtils';
import {hasEReceipt, hasMissingSmartscanFields, hasReceipt, hasReceiptSource, isReceiptBeingScanned} from '@libs/TransactionUtils';
import type {AvatarSource} from '@libs/UserUtils';
import type {FileObject} from '@pages/media/AttachmentModalScreen/types';
import variables from '@styles/variables';
import {detachReceipt} from '@userActions/IOU';
import type {IOUAction, IOUType} from '@src/CONST';
import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type * as OnyxTypes from '@src/types/onyx';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import viewRef from '@src/types/utils/viewRef';
import type {FileObject} from './types';

type OnValidateFileCallback = (file: FileObject | undefined, setFile: (file: FileObject | undefined) => void) => void;

type OnCloseOptions = {
shouldCallDirectly?: boolean;
onAfterClose?: () => void;
};

type AttachmentModalBaseContentProps = {
/** Optional source (URL, SVG function) for the image shown. If not passed in via props must be specified when modal is opened. */
source?: AvatarSource;

/** The id of the attachment. */
attachmentID?: string;

/** Fallback source (URL, SVG function) for the image shown. */
fallbackSource?: AvatarSource;

/** Optional file object to be used for the attachment. If not passed in via props must be specified when modal is opened. */
file?: FileObject;

/** Optional original filename when uploading */
originalFileName?: string;

/** Whether source url requires authentication */
isAuthTokenRequired?: boolean;

/** Determines if download Button should be shown or not */
allowDownload?: boolean;

/** Determines if the receipt comes from track expense action */
isTrackExpenseAction?: boolean;

/** Title shown in the header of the modal */
headerTitle?: string;

/** The report that has this attachment */
report?: OnyxEntry<OnyxTypes.Report>;

/** The ID of the current report */
reportID?: string;

/** The type of the attachment */
type?: ValueOf<typeof CONST.ATTACHMENT_TYPE>;

/** The iou action of the expense creation flow of which we are displaying the receipt for. */
iouAction?: IOUAction;

/** The iou type of the expense creation flow of which we are displaying the receipt for. */
iouType?: IOUType;

/** The id of the draft transaction linked to the receipt. */
draftTransactionID?: string;

/** If the attachment originates from a note, the accountID will represent the author of that note. */
accountID?: number;

/** The data is loading or not */
isLoading?: boolean;

/** Should display not found page or not */
shouldShowNotFoundPage?: boolean;

/** Denotes whether it is a workspace avatar or not */
isWorkspaceAvatar?: boolean;

/** Denotes whether it can be an icon (ex: SVG) */
maybeIcon?: boolean;

/** Whether it is a receipt attachment or not */
isReceiptAttachment?: boolean;

/** Determines if the user can edit the receipt or not */
canEditReceipt?: boolean;

/** Determines if the user can delete the receipt or not */
canDeleteReceipt?: boolean;

/** Determines if the send button should be disabled or not */
shouldDisableSendButton?: boolean;

/** Determines if the help button should be displayed or not */
shouldDisplayHelpButton?: boolean;

/** The link of the attachment */
attachmentLink?: string;

/** Determines if the attachment is invalid or not */
isAttachmentInvalid?: boolean;

/** Determines if the attachment is invalid or not */
attachmentInvalidReason?: TranslationPaths | null;

/** Determines the title of the invalid reason modal */
attachmentInvalidReasonTitle?: TranslationPaths | null;

/** Ref to the submit button */
submitRef?: RefObject<View | HTMLElement | null>;

/** Determines if the delete receipt confirm modal is visible or not */
isDeleteReceiptConfirmModalVisible?: boolean;

/** Optional callback to fire when we want to preview an image and approve it for use. */
onConfirm?: (file: FileObject) => void;

/** Callback triggered when the modal is closed */
onClose?: (options?: OnCloseOptions) => void;

/** Callback triggered when the confirm modal is closed */
onConfirmModalClose?: () => void;

/** Callback triggered when the delete receipt modal is shown */
onRequestDeleteReceipt?: () => void;

/** Callback triggered when the delete receipt is confirmed */
onDeleteReceipt?: () => void;

/** Optional callback to fire when we want to do something after attachment carousel changes. */
onCarouselAttachmentChange?: (attachment: Attachment) => void;

/** Optional callback to fire when we want to validate the file. */
onValidateFile?: OnValidateFileCallback;
};
import type {AttachmentModalBaseContentProps} from './types';

function AttachmentModalBaseContent({
source = '',
Expand Down Expand Up @@ -578,4 +450,4 @@ AttachmentModalBaseContent.displayName = 'AttachmentModalBaseContent';

export default memo(AttachmentModalBaseContent);

export type {AttachmentModalBaseContentProps, OnValidateFileCallback, OnCloseOptions};
export type {AttachmentModalBaseContentProps};
Loading
Loading