From cdc0c704ac837fd17d3d54de7847023dc7bbb596 Mon Sep 17 00:00:00 2001
From: Situ Chandra Shil <108292595+situchan@users.noreply.github.com>
Date: Wed, 31 Jan 2024 19:47:01 +0600
Subject: [PATCH] Revert "[TS migration] Migrate 'AttachmentModal.js' component
 to TypeScript"

---
 ...AttachmentModal.tsx => AttachmentModal.js} | 429 +++++++++---------
 .../ReportActionItemImage.tsx                 |  29 +-
 src/libs/TransactionUtils.ts                  |   4 +-
 src/pages/ReportAvatar.tsx                    |   3 +-
 src/pages/home/report/ReportAttachments.tsx   |  17 +-
 src/pages/settings/Profile/ProfileAvatar.tsx  |   3 +-
 src/pages/workspace/WorkspaceAvatar.tsx       |   3 +-
 .../utils/generators/ModalStyleUtils.ts       |   4 +-
 src/types/utils/ModalType.ts                  |   6 -
 9 files changed, 255 insertions(+), 243 deletions(-)
 rename src/components/{AttachmentModal.tsx => AttachmentModal.js} (62%)
 delete mode 100644 src/types/utils/ModalType.ts

diff --git a/src/components/AttachmentModal.tsx b/src/components/AttachmentModal.js
similarity index 62%
rename from src/components/AttachmentModal.tsx
rename to src/components/AttachmentModal.js
index f3e8ed316c52..46ce4cf63f26 100755
--- a/src/components/AttachmentModal.tsx
+++ b/src/components/AttachmentModal.js
@@ -1,32 +1,32 @@
 import Str from 'expensify-common/lib/str';
+import lodashExtend from 'lodash/extend';
+import lodashGet from 'lodash/get';
+import PropTypes from 'prop-types';
 import React, {useCallback, useEffect, useMemo, useState} from 'react';
 import {Animated, Keyboard, View} from 'react-native';
 import {GestureHandlerRootView} from 'react-native-gesture-handler';
 import {withOnyx} from 'react-native-onyx';
-import type {OnyxEntry} from 'react-native-onyx';
+import _ from 'underscore';
 import useLocalize from '@hooks/useLocalize';
 import useNetwork from '@hooks/useNetwork';
 import useStyleUtils from '@hooks/useStyleUtils';
+import useTheme from '@hooks/useTheme';
 import useThemeStyles from '@hooks/useThemeStyles';
 import useWindowDimensions from '@hooks/useWindowDimensions';
 import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL';
+import compose from '@libs/compose';
 import fileDownload from '@libs/fileDownload';
 import * as FileUtils from '@libs/fileDownload/FileUtils';
 import Navigation from '@libs/Navigation/Navigation';
 import * as ReportActionsUtils from '@libs/ReportActionsUtils';
 import * as TransactionUtils from '@libs/TransactionUtils';
 import useNativeDriver from '@libs/useNativeDriver';
-import type {AvatarSource} from '@libs/UserUtils';
+import reportPropTypes from '@pages/reportPropTypes';
 import variables from '@styles/variables';
 import * as IOU from '@userActions/IOU';
 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 type {EmptyObject} from '@src/types/utils/EmptyObject';
-import {isEmptyObject} from '@src/types/utils/EmptyObject';
-import type ModalType from '@src/types/utils/ModalType';
 import AttachmentCarousel from './Attachments/AttachmentCarousel';
 import AttachmentView from './Attachments/AttachmentView';
 import BlockingView from './BlockingViews/BlockingView';
@@ -37,156 +37,127 @@ import HeaderGap from './HeaderGap';
 import HeaderWithBackButton from './HeaderWithBackButton';
 import * as Expensicons from './Icon/Expensicons';
 import * as Illustrations from './Icon/Illustrations';
+import sourcePropTypes from './Image/sourcePropTypes';
 import Modal from './Modal';
 import SafeAreaConsumer from './SafeAreaConsumer';
+import transactionPropTypes from './transactionPropTypes';
+import withLocalize, {withLocalizePropTypes} from './withLocalize';
+import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions';
 
 /**
  * Modal render prop component that exposes modal launching triggers that can be used
  * to display a full size image or PDF modally with optional confirmation button.
  */
 
-type AttachmentModalOnyxProps = {
-    /** The transaction associated with the receipt attachment, if any */
-    transaction: OnyxEntry<OnyxTypes.Transaction>;
-
-    /** The report associated with the receipt attachment, if any */
-    parentReport: OnyxEntry<OnyxTypes.Report>;
-
-    /** The policy associated with the receipt attachment, if any */
-    policy: OnyxEntry<OnyxTypes.Policy>;
-
-    /** The list of report actions associated with the receipt attachment, if any */
-    parentReportActions: OnyxEntry<OnyxTypes.ReportActions>;
-};
-
-type Attachment = {
-    source: AvatarSource;
-    isAuthTokenRequired: boolean;
-    file: FileObject;
-    isReceipt: boolean;
-    hasBeenFlagged?: boolean;
-    reportActionID?: string;
-};
-
-type ImagePickerResponse = {
-    height: number;
-    name: string;
-    size: number;
-    type: string;
-    uri: string;
-    width: number;
-};
-
-type FileObject = File | ImagePickerResponse;
-
-type ChildrenProps = {
-    displayFileInModal: (data: FileObject) => void;
-    show: () => void;
-};
-
-type AttachmentModalProps = AttachmentModalOnyxProps & {
+const propTypes = {
     /** Optional source (URL, SVG function) for the image shown. If not passed in via props must be specified when modal is opened. */
-    source?: AvatarSource;
+    source: PropTypes.oneOfType([PropTypes.string, sourcePropTypes]),
 
     /** Optional callback to fire when we want to preview an image and approve it for use. */
-    onConfirm?: ((file: Partial<FileObject>) => void) | null;
+    onConfirm: PropTypes.func,
 
     /** Whether the modal should be open by default */
-    defaultOpen?: boolean;
+    defaultOpen: PropTypes.bool,
+
+    /** Optional callback to fire when we want to do something after modal show. */
+    onModalShow: PropTypes.func,
+
+    /** Optional callback to fire when we want to do something after modal hide. */
+    onModalHide: PropTypes.func,
 
     /** Trigger when we explicity click close button in ProfileAttachment modal */
-    onModalClose?: () => void;
+    onModalClose: PropTypes.func,
+
+    /** Optional callback to fire when we want to do something after attachment carousel changes. */
+    onCarouselAttachmentChange: PropTypes.func,
 
     /** Optional original filename when uploading */
-    originalFileName?: string;
+    originalFileName: PropTypes.string,
+
+    /** A function as a child to pass modal launching methods to */
+    children: PropTypes.func,
 
     /** Whether source url requires authentication */
-    isAuthTokenRequired?: boolean;
+    isAuthTokenRequired: PropTypes.bool,
 
     /** Determines if download Button should be shown or not */
-    allowDownload?: boolean;
+    allowDownload: PropTypes.bool,
 
     /** Title shown in the header of the modal */
-    headerTitle?: string;
+    headerTitle: PropTypes.string,
 
     /** The report that has this attachment */
-    report?: OnyxEntry<OnyxTypes.Report> | EmptyObject;
-
-    /** Optional callback to fire when we want to do something after modal show. */
-    onModalShow?: () => void;
+    report: reportPropTypes,
 
-    /** Optional callback to fire when we want to do something after modal hide. */
-    onModalHide?: () => void;
+    /** The transaction associated with the receipt attachment, if any */
+    transaction: transactionPropTypes,
 
     /** The data is loading or not */
-    isLoading?: boolean;
+    isLoading: PropTypes.bool,
 
     /** Should display not found page or not */
-    shouldShowNotFoundPage?: boolean;
+    shouldShowNotFoundPage: PropTypes.bool,
 
-    /** Optional callback to fire when we want to do something after attachment carousel changes. */
-    onCarouselAttachmentChange?: (attachment: Attachment) => void;
+    ...withLocalizePropTypes,
+
+    ...windowDimensionsPropTypes,
 
     /** Denotes whether it is a workspace avatar or not */
-    isWorkspaceAvatar?: boolean;
+    isWorkspaceAvatar: PropTypes.bool,
 
     /** Whether it is a receipt attachment or not */
-    isReceiptAttachment?: boolean;
-
-    /** A function as a child to pass modal launching methods to */
-    children?: React.FC<ChildrenProps>;
+    isReceiptAttachment: PropTypes.bool,
 
-    fallbackSource?: AvatarSource;
+    /** Whether the receipt can be replaced */
+    canEditReceipt: PropTypes.bool,
+};
 
-    canEditReceipt?: boolean;
+const defaultProps = {
+    source: '',
+    onConfirm: null,
+    defaultOpen: false,
+    originalFileName: '',
+    children: null,
+    isAuthTokenRequired: false,
+    allowDownload: false,
+    headerTitle: null,
+    report: {},
+    transaction: {},
+    onModalShow: () => {},
+    onModalHide: () => {},
+    onCarouselAttachmentChange: () => {},
+    isWorkspaceAvatar: false,
+    onModalClose: () => {},
+    isLoading: false,
+    shouldShowNotFoundPage: false,
+    isReceiptAttachment: false,
+    canEditReceipt: false,
 };
 
-function AttachmentModal({
-    source = '',
-    onConfirm,
-    defaultOpen = false,
-    originalFileName = '',
-    isAuthTokenRequired = false,
-    allowDownload = false,
-    report,
-    onModalShow = () => {},
-    onModalHide = () => {},
-    onCarouselAttachmentChange = () => {},
-    isReceiptAttachment = false,
-    isWorkspaceAvatar = false,
-    transaction,
-    parentReport,
-    parentReportActions,
-    headerTitle,
-    policy,
-    children,
-    fallbackSource,
-    canEditReceipt = false,
-    onModalClose = () => {},
-    isLoading = false,
-    shouldShowNotFoundPage = false,
-}: AttachmentModalProps) {
+function AttachmentModal(props) {
+    const theme = useTheme();
     const styles = useThemeStyles();
     const StyleUtils = useStyleUtils();
-    const [isModalOpen, setIsModalOpen] = useState(defaultOpen);
+    const [isModalOpen, setIsModalOpen] = useState(props.defaultOpen);
     const [shouldLoadAttachment, setShouldLoadAttachment] = useState(false);
     const [isAttachmentInvalid, setIsAttachmentInvalid] = useState(false);
     const [isDeleteReceiptConfirmModalVisible, setIsDeleteReceiptConfirmModalVisible] = useState(false);
-    const [isAuthTokenRequiredState, setIsAuthTokenRequiredState] = useState(isAuthTokenRequired);
-    const [attachmentInvalidReasonTitle, setAttachmentInvalidReasonTitle] = useState<TranslationPaths | null>(null);
-    const [attachmentInvalidReason, setAttachmentInvalidReason] = useState<TranslationPaths | null>(null);
-    const [sourceState, setSourceState] = useState(() => source);
-    const [modalType, setModalType] = useState<ModalType>(CONST.MODAL.MODAL_TYPE.CENTERED_UNSWIPEABLE);
+    const [isAuthTokenRequired, setIsAuthTokenRequired] = useState(props.isAuthTokenRequired);
+    const [attachmentInvalidReasonTitle, setAttachmentInvalidReasonTitle] = useState('');
+    const [attachmentInvalidReason, setAttachmentInvalidReason] = useState(null);
+    const [source, setSource] = useState(() => props.source);
+    const [modalType, setModalType] = useState(CONST.MODAL.MODAL_TYPE.CENTERED_UNSWIPEABLE);
     const [isConfirmButtonDisabled, setIsConfirmButtonDisabled] = useState(false);
     const [confirmButtonFadeAnimation] = useState(() => new Animated.Value(1));
     const [isDownloadButtonReadyToBeShown, setIsDownloadButtonReadyToBeShown] = React.useState(true);
-    const {windowWidth, isSmallScreenWidth} = useWindowDimensions();
-    const isOverlayModalVisible = (isReceiptAttachment && isDeleteReceiptConfirmModalVisible) || (!isReceiptAttachment && isAttachmentInvalid);
+    const {windowWidth} = useWindowDimensions();
+
+    const isOverlayModalVisible = (props.isReceiptAttachment && isDeleteReceiptConfirmModalVisible) || (!props.isReceiptAttachment && isAttachmentInvalid);
 
-    const [file, setFile] = useState<Partial<FileObject> | undefined>(
-        originalFileName
+    const [file, setFile] = useState(
+        props.originalFileName
             ? {
-                  name: originalFileName,
+                  name: props.originalFileName,
               }
             : undefined,
     );
@@ -194,35 +165,41 @@ function AttachmentModal({
     const {isOffline} = useNetwork();
 
     useEffect(() => {
-        setFile(originalFileName ? {name: originalFileName} : undefined);
-    }, [originalFileName]);
+        setFile(props.originalFileName ? {name: props.originalFileName} : undefined);
+    }, [props.originalFileName]);
+
+    const onCarouselAttachmentChange = props.onCarouselAttachmentChange;
 
     /**
      * Keeps the attachment source in sync with the attachment displayed currently in the carousel.
+     * @param {{ source: String, isAuthTokenRequired: Boolean, file: { name: string }, isReceipt: Boolean }} attachment
      */
     const onNavigate = useCallback(
-        (attachment: Attachment) => {
-            setSourceState(attachment.source);
+        (attachment) => {
+            setSource(attachment.source);
             setFile(attachment.file);
-            setIsAuthTokenRequiredState(attachment.isAuthTokenRequired);
+            setIsAuthTokenRequired(attachment.isAuthTokenRequired);
             onCarouselAttachmentChange(attachment);
         },
         [onCarouselAttachmentChange],
     );
 
     /**
-     * If our attachment is a PDF, return the unswipeablge Modal type.
+     * If our attachment is a PDF, return the unswipeable Modal type.
+     * @param {String} sourceURL
+     * @param {Object} _file
+     * @returns {String}
      */
     const getModalType = useCallback(
-        (sourceURL: string, fileObject: FileObject) =>
-            sourceURL && (Str.isPDF(sourceURL) || (fileObject && Str.isPDF(fileObject.name || translate('attachmentView.unknownFilename'))))
+        (sourceURL, _file) =>
+            sourceURL && (Str.isPDF(sourceURL) || (_file && Str.isPDF(_file.name || translate('attachmentView.unknownFilename'))))
                 ? CONST.MODAL.MODAL_TYPE.CENTERED_UNSWIPEABLE
                 : CONST.MODAL.MODAL_TYPE.CENTERED,
         [translate],
     );
 
     const setDownloadButtonVisibility = useCallback(
-        (isButtonVisible: boolean) => {
+        (isButtonVisible) => {
             if (isDownloadButtonReadyToBeShown === isButtonVisible) {
                 return;
             }
@@ -235,19 +212,17 @@ function AttachmentModal({
      * Download the currently viewed attachment.
      */
     const downloadAttachment = useCallback(() => {
-        let sourceURL = sourceState;
-        if (isAuthTokenRequiredState && typeof sourceURL === 'string') {
+        let sourceURL = source;
+        if (isAuthTokenRequired) {
             sourceURL = addEncryptedAuthTokenToURL(sourceURL);
         }
 
-        if (typeof sourceURL === 'string') {
-            fileDownload(sourceURL, file?.name ?? '');
-        }
+        fileDownload(sourceURL, lodashGet(file, 'name', ''));
 
         // At ios, if the keyboard is open while opening the attachment, then after downloading
         // the attachment keyboard will show up. So, to fix it we need to dismiss the keyboard.
         Keyboard.dismiss();
-    }, [isAuthTokenRequiredState, sourceState, file]);
+    }, [isAuthTokenRequired, source, file]);
 
     /**
      * Execute the onConfirm callback and close the modal.
@@ -259,13 +234,13 @@ function AttachmentModal({
             return;
         }
 
-        if (onConfirm) {
-            onConfirm(Object.assign(file ?? {}, {source: sourceState}));
+        if (props.onConfirm) {
+            props.onConfirm(lodashExtend(file, {source}));
         }
 
         setIsModalOpen(false);
         // eslint-disable-next-line react-hooks/exhaustive-deps
-    }, [isModalOpen, isConfirmButtonDisabled, onConfirm, file, sourceState]);
+    }, [isModalOpen, isConfirmButtonDisabled, props.onConfirm, file, source]);
 
     /**
      * Close the confirm modals.
@@ -279,20 +254,24 @@ function AttachmentModal({
      * Detach the receipt and close the modal.
      */
     const deleteAndCloseModal = useCallback(() => {
-        IOU.detachReceipt(transaction?.transactionID);
+        IOU.detachReceipt(props.transaction.transactionID, props.report.reportID);
         setIsDeleteReceiptConfirmModalVisible(false);
-        Navigation.dismissModal(report?.reportID);
-    }, [transaction, report]);
+        Navigation.dismissModal(props.report.reportID);
+    }, [props.transaction, props.report]);
 
-    const isValidFile = useCallback((fileObject: FileObject) => {
-        if (fileObject.size > CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE) {
+    /**
+     * @param {Object} _file
+     * @returns {Boolean}
+     */
+    const isValidFile = useCallback((_file) => {
+        if (lodashGet(_file, 'size', 0) > CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE) {
             setIsAttachmentInvalid(true);
             setAttachmentInvalidReasonTitle('attachmentPicker.attachmentTooLarge');
             setAttachmentInvalidReason('attachmentPicker.sizeExceeded');
             return false;
         }
 
-        if (fileObject.size < CONST.API_ATTACHMENT_VALIDATIONS.MIN_SIZE) {
+        if (lodashGet(_file, 'size', 0) < CONST.API_ATTACHMENT_VALIDATIONS.MIN_SIZE) {
             setIsAttachmentInvalid(true);
             setAttachmentInvalidReasonTitle('attachmentPicker.attachmentTooSmall');
             setAttachmentInvalidReason('attachmentPicker.sizeNotMet');
@@ -301,9 +280,12 @@ function AttachmentModal({
 
         return true;
     }, []);
-
-    const isDirectoryCheck = useCallback((data: FileObject) => {
-        if ('webkitGetAsEntry' in data && typeof data.webkitGetAsEntry === 'function' && data.webkitGetAsEntry().isDirectory) {
+    /**
+     * @param {Object} _data
+     * @returns {Boolean}
+     */
+    const isDirectoryCheck = useCallback((_data) => {
+        if (typeof _data.webkitGetAsEntry === 'function' && _data.webkitGetAsEntry().isDirectory) {
             setIsAttachmentInvalid(true);
             setAttachmentInvalidReasonTitle('attachmentPicker.attachmentError');
             setAttachmentInvalidReason('attachmentPicker.folderNotAllowedMessage');
@@ -312,14 +294,17 @@ function AttachmentModal({
         return true;
     }, []);
 
+    /**
+     * @param {Object} _data
+     */
     const validateAndDisplayFileToUpload = useCallback(
-        (data: FileObject) => {
-            if (!isDirectoryCheck(data)) {
+        (_data) => {
+            if (!isDirectoryCheck(_data)) {
                 return;
             }
-            let fileObject = data;
-            if ('getAsFile' in data && typeof data.getAsFile === 'function') {
-                fileObject = data.getAsFile();
+            let fileObject = _data;
+            if (typeof _data.getAsFile === 'function') {
+                fileObject = _data.getAsFile();
             }
             if (!fileObject) {
                 return;
@@ -342,13 +327,13 @@ function AttachmentModal({
                 const inputSource = URL.createObjectURL(updatedFile);
                 const inputModalType = getModalType(inputSource, updatedFile);
                 setIsModalOpen(true);
-                setSourceState(inputSource);
+                setSource(inputSource);
                 setFile(updatedFile);
                 setModalType(inputModalType);
             } else {
                 const inputModalType = getModalType(fileObject.uri, fileObject);
                 setIsModalOpen(true);
-                setSourceState(fileObject.uri);
+                setSource(fileObject.uri);
                 setFile(fileObject);
                 setModalType(inputModalType);
             }
@@ -362,10 +347,10 @@ function AttachmentModal({
      * we're only updating the opacity of the confirm button, we must also conditionally
      * disable it.
      *
-     * @param shouldFadeOut If true, fade out confirm button. Otherwise fade in.
+     * @param {Boolean} shouldFadeOut If true, fade out confirm button. Otherwise fade in.
      */
     const updateConfirmButtonVisibility = useCallback(
-        (shouldFadeOut: boolean) => {
+        (shouldFadeOut) => {
             setIsConfirmButtonDisabled(shouldFadeOut);
             const toValue = shouldFadeOut ? 0 : 1;
 
@@ -384,12 +369,12 @@ function AttachmentModal({
     const closeModal = useCallback(() => {
         setIsModalOpen(false);
 
-        if (typeof onModalClose === 'function') {
-            onModalClose();
+        if (typeof props.onModalClose === 'function') {
+            props.onModalClose();
         }
 
         // eslint-disable-next-line react-hooks/exhaustive-deps
-    }, [onModalClose]);
+    }, [props.onModalClose]);
 
     /**
      *  open the modal
@@ -399,33 +384,33 @@ function AttachmentModal({
     }, []);
 
     useEffect(() => {
-        setSourceState(() => source);
-    }, [source]);
+        setSource(() => props.source);
+    }, [props.source]);
 
     useEffect(() => {
-        setIsAuthTokenRequiredState(isAuthTokenRequired);
-    }, [isAuthTokenRequired]);
+        setIsAuthTokenRequired(props.isAuthTokenRequired);
+    }, [props.isAuthTokenRequired]);
 
-    const sourceForAttachmentView = sourceState || source;
+    const sourceForAttachmentView = source || props.source;
 
     const threeDotsMenuItems = useMemo(() => {
-        if (!isReceiptAttachment || !parentReport || !parentReportActions) {
+        if (!props.isReceiptAttachment || !props.parentReport || !props.parentReportActions) {
             return [];
         }
 
         const menuItems = [];
-        if (canEditReceipt) {
+        if (props.canEditReceipt) {
             menuItems.push({
                 icon: Expensicons.Camera,
-                text: translate('common.replace'),
+                text: props.translate('common.replace'),
                 onSelected: () => {
                     closeModal();
                     Navigation.navigate(
                         ROUTES.MONEY_REQUEST_STEP_SCAN.getRoute(
                             CONST.IOU.ACTION.EDIT,
                             CONST.IOU.TYPE.REQUEST,
-                            transaction?.transactionID ?? '',
-                            report?.reportID ?? '',
+                            props.transaction.transactionID,
+                            props.report.reportID,
                             Navigation.getActiveRouteWithoutParams(),
                         ),
                     );
@@ -435,14 +420,14 @@ function AttachmentModal({
         if (!isOffline) {
             menuItems.push({
                 icon: Expensicons.Download,
-                text: translate('common.download'),
-                onSelected: () => downloadAttachment(),
+                text: props.translate('common.download'),
+                onSelected: () => downloadAttachment(source),
             });
         }
-        if (TransactionUtils.hasReceipt(transaction) && !TransactionUtils.isReceiptBeingScanned(transaction) && canEditReceipt) {
+        if (TransactionUtils.hasReceipt(props.transaction) && !TransactionUtils.isReceiptBeingScanned(props.transaction) && props.canEditReceipt) {
             menuItems.push({
                 icon: Expensicons.Trashcan,
-                text: translate('receipt.deleteReceipt'),
+                text: props.translate('receipt.deleteReceipt'),
                 onSelected: () => {
                     setIsDeleteReceiptConfirmModalVisible(true);
                 },
@@ -450,17 +435,17 @@ function AttachmentModal({
         }
         return menuItems;
         // eslint-disable-next-line react-hooks/exhaustive-deps
-    }, [isReceiptAttachment, parentReport, parentReportActions, policy, transaction, file, sourceState]);
+    }, [props.isReceiptAttachment, props.parentReport, props.parentReportActions, props.policy, props.transaction, file, source]);
 
     // There are a few things that shouldn't be set until we absolutely know if the file is a receipt or an attachment.
     // props.isReceiptAttachment will be null until its certain what the file is, in which case it will then be true|false.
-    let headerTitleNew = headerTitle;
+    let headerTitle = props.headerTitle;
     let shouldShowDownloadButton = false;
     let shouldShowThreeDotsButton = false;
-    if (!isEmptyObject(report)) {
-        headerTitleNew = translate(isReceiptAttachment ? 'common.receipt' : 'common.attachment');
-        shouldShowDownloadButton = allowDownload && isDownloadButtonReadyToBeShown && !isReceiptAttachment && !isOffline;
-        shouldShowThreeDotsButton = isReceiptAttachment && isModalOpen && threeDotsMenuItems.length !== 0;
+    if (!_.isEmpty(props.report)) {
+        headerTitle = translate(props.isReceiptAttachment ? 'common.receipt' : 'common.attachment');
+        shouldShowDownloadButton = props.allowDownload && isDownloadButtonReadyToBeShown && !props.isReceiptAttachment && !isOffline;
+        shouldShowThreeDotsButton = props.isReceiptAttachment && isModalOpen && threeDotsMenuItems.length !== 0;
     }
 
     return (
@@ -470,25 +455,26 @@ function AttachmentModal({
                 onSubmit={submitAndClose}
                 onClose={isOverlayModalVisible ? closeConfirmModal : closeModal}
                 isVisible={isModalOpen}
+                backgroundColor={theme.componentBG}
                 onModalShow={() => {
-                    onModalShow();
+                    props.onModalShow();
                     setShouldLoadAttachment(true);
                 }}
-                onModalHide={() => {
-                    onModalHide();
+                onModalHide={(e) => {
+                    props.onModalHide(e);
                     setShouldLoadAttachment(false);
                 }}
                 propagateSwipe
             >
                 <GestureHandlerRootView style={styles.flex1}>
-                    {isSmallScreenWidth && <HeaderGap />}
+                    {props.isSmallScreenWidth && <HeaderGap />}
                     <HeaderWithBackButton
-                        title={headerTitleNew}
+                        title={headerTitle}
                         shouldShowBorderBottom
                         shouldShowDownloadButton={shouldShowDownloadButton}
-                        onDownloadButtonPress={() => downloadAttachment()}
-                        shouldShowCloseButton={!isSmallScreenWidth}
-                        shouldShowBackButton={isSmallScreenWidth}
+                        onDownloadButtonPress={() => downloadAttachment(source)}
+                        shouldShowCloseButton={!props.isSmallScreenWidth}
+                        shouldShowBackButton={props.isSmallScreenWidth}
                         onBackButtonPress={closeModal}
                         onCloseButtonPress={closeModal}
                         shouldShowThreeDotsButton={shouldShowThreeDotsButton}
@@ -497,8 +483,8 @@ function AttachmentModal({
                         shouldOverlayDots
                     />
                     <View style={styles.imageModalImageCenterContainer}>
-                        {isLoading && <FullScreenLoadingIndicator />}
-                        {shouldShowNotFoundPage && !isLoading && (
+                        {props.isLoading && <FullScreenLoadingIndicator />}
+                        {props.shouldShowNotFoundPage && !props.isLoading && (
                             <BlockingView
                                 icon={Illustrations.ToddBehindCloud}
                                 iconWidth={variables.modalTopIconWidth}
@@ -510,53 +496,52 @@ function AttachmentModal({
                                 onLinkPress={() => Navigation.dismissModal()}
                             />
                         )}
-                        {!isEmptyObject(report) && !isReceiptAttachment ? (
+                        {!_.isEmpty(props.report) && !props.isReceiptAttachment ? (
                             <AttachmentCarousel
-                                report={report}
+                                report={props.report}
                                 onNavigate={onNavigate}
-                                source={source}
+                                source={props.source}
                                 onToggleKeyboard={updateConfirmButtonVisibility}
                                 setDownloadButtonVisibility={setDownloadButtonVisibility}
                             />
                         ) : (
-                            !!sourceForAttachmentView &&
+                            Boolean(sourceForAttachmentView) &&
                             shouldLoadAttachment &&
-                            !isLoading &&
-                            !shouldShowNotFoundPage && (
+                            !props.isLoading &&
+                            !props.shouldShowNotFoundPage && (
                                 <AttachmentView
-                                    // @ts-expect-error TODO: Remove this once Attachments (https://github.com/Expensify/App/issues/24969) is migrated to TypeScript.
                                     containerStyles={[styles.mh5]}
                                     source={sourceForAttachmentView}
                                     isAuthTokenRequired={isAuthTokenRequired}
                                     file={file}
                                     onToggleKeyboard={updateConfirmButtonVisibility}
-                                    isWorkspaceAvatar={isWorkspaceAvatar}
-                                    fallbackSource={fallbackSource}
+                                    isWorkspaceAvatar={props.isWorkspaceAvatar}
+                                    fallbackSource={props.fallbackSource}
                                     isUsedInAttachmentModal
-                                    transactionID={transaction?.transactionID}
+                                    transactionID={props.transaction.transactionID}
                                 />
                             )
                         )}
                     </View>
                     {/* If we have an onConfirm method show a confirmation button */}
-                    {!!onConfirm && (
+                    {Boolean(props.onConfirm) && (
                         <SafeAreaConsumer>
                             {({safeAreaPaddingBottomStyle}) => (
                                 <Animated.View style={[StyleUtils.fade(confirmButtonFadeAnimation), safeAreaPaddingBottomStyle]}>
                                     <Button
                                         success
-                                        style={[styles.buttonConfirm, isSmallScreenWidth ? {} : styles.attachmentButtonBigScreen]}
+                                        style={[styles.buttonConfirm, props.isSmallScreenWidth ? {} : styles.attachmentButtonBigScreen]}
                                         textStyles={[styles.buttonConfirmText]}
                                         text={translate('common.send')}
                                         onPress={submitAndClose}
-                                        isDisabled={isConfirmButtonDisabled}
+                                        disabled={isConfirmButtonDisabled}
                                         pressOnEnter
                                     />
                                 </Animated.View>
                             )}
                         </SafeAreaConsumer>
                     )}
-                    {isReceiptAttachment && (
+                    {props.isReceiptAttachment && (
                         <ConfirmModal
                             title={translate('receipt.deleteReceipt')}
                             isVisible={isDeleteReceiptConfirmModalVisible}
@@ -570,7 +555,7 @@ function AttachmentModal({
                     )}
                 </GestureHandlerRootView>
             </Modal>
-            {!isReceiptAttachment && (
+            {!props.isReceiptAttachment && (
                 <ConfirmModal
                     title={attachmentInvalidReasonTitle ? translate(attachmentInvalidReasonTitle) : ''}
                     onConfirm={closeConfirmModal}
@@ -582,34 +567,44 @@ function AttachmentModal({
                 />
             )}
 
-            {children?.({
-                displayFileInModal: validateAndDisplayFileToUpload,
-                show: openModal,
-            })}
+            {props.children &&
+                props.children({
+                    displayFileInModal: validateAndDisplayFileToUpload,
+                    show: openModal,
+                })}
         </>
     );
 }
 
+AttachmentModal.propTypes = propTypes;
+AttachmentModal.defaultProps = defaultProps;
 AttachmentModal.displayName = 'AttachmentModal';
-
-export default withOnyx<AttachmentModalProps, AttachmentModalOnyxProps>({
-    transaction: {
-        key: ({report}) => {
-            const parentReportAction = ReportActionsUtils.getReportAction(report?.parentReportID ?? '', report?.parentReportActionID ?? '');
-            const transactionID = parentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? parentReportAction?.originalMessage.IOUTransactionID ?? '0' : '0';
-            return `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`;
+export default compose(
+    withWindowDimensions,
+    withLocalize,
+    withOnyx({
+        transaction: {
+            key: ({report}) => {
+                if (!report) {
+                    return undefined;
+                }
+                const parentReportAction = ReportActionsUtils.getReportAction(report.parentReportID, report.parentReportActionID);
+                const transactionID = lodashGet(parentReportAction, ['originalMessage', 'IOUTransactionID'], 0);
+                return `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`;
+            },
+        },
+        parentReport: {
+            key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT}${report ? report.parentReportID : '0'}`,
+        },
+        policy: {
+            key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : '0'}`,
+        },
+        parentReportActions: {
+            key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report ? report.parentReportID : '0'}`,
+            canEvict: false,
+        },
+        session: {
+            key: ONYXKEYS.SESSION,
         },
-    },
-    parentReport: {
-        key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT}${report ? report.parentReportID : '0'}`,
-    },
-    policy: {
-        key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : '0'}`,
-    },
-    parentReportActions: {
-        key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report ? report.parentReportID : '0'}`,
-        canEvict: false,
-    },
-})(AttachmentModal);
-
-export type {Attachment};
+    }),
+)(AttachmentModal);
diff --git a/src/components/ReportActionItem/ReportActionItemImage.tsx b/src/components/ReportActionItem/ReportActionItemImage.tsx
index e71eb6834cc7..710adeb1589e 100644
--- a/src/components/ReportActionItem/ReportActionItemImage.tsx
+++ b/src/components/ReportActionItem/ReportActionItemImage.tsx
@@ -1,8 +1,8 @@
 import Str from 'expensify-common/lib/str';
 import React from 'react';
 import type {ReactElement} from 'react';
-import type {ImageSourcePropType, ViewStyle} from 'react-native';
 import {View} from 'react-native';
+import type {ImageSourcePropType} from 'react-native';
 import AttachmentModal from '@components/AttachmentModal';
 import EReceiptThumbnail from '@components/EReceiptThumbnail';
 import Image from '@components/Image';
@@ -79,25 +79,30 @@ function ReportActionItemImage({thumbnail, image, enablePreviewModal = false, tr
         return (
             <ShowContextMenuContext.Consumer>
                 {({report}) => (
+                    // @ts-expect-error TODO: Remove this once AttachmentModal (https://github.com/Expensify/App/issues/25130) is migrated to TypeScript.
                     <AttachmentModal
                         source={imageSource}
                         isAuthTokenRequired={!isLocalFile}
                         report={report}
                         isReceiptAttachment
                         canEditReceipt={canEditReceipt}
-                        allowDownload
+                        allowToDownload
                         originalFileName={transaction?.filename}
                     >
-                        {({show}) => (
-                            <PressableWithoutFocus
-                                style={[styles.w100, styles.h100, styles.noOutline as ViewStyle]}
-                                onPress={show}
-                                accessibilityRole={CONST.ROLE.BUTTON}
-                                accessibilityLabel={translate('accessibilityHints.viewAttachment')}
-                            >
-                                {receiptImageComponent}
-                            </PressableWithoutFocus>
-                        )}
+                        {
+                            // @ts-expect-error TODO: Remove this once AttachmentModal (https://github.com/Expensify/App/issues/25130) is migrated to TypeScript.
+                            ({show}) => (
+                                <PressableWithoutFocus
+                                    // @ts-expect-error TODO: Remove this once AttachmentModal (https://github.com/Expensify/App/issues/25130) is migrated to TypeScript.
+                                    style={[styles.noOutline, styles.w100, styles.h100]}
+                                    onPress={show}
+                                    accessibilityRole={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON}
+                                    accessibilityLabel={translate('accessibilityHints.viewAttachment')}
+                                >
+                                    {receiptImageComponent}
+                                </PressableWithoutFocus>
+                            )
+                        }
                     </AttachmentModal>
                 )}
             </ShowContextMenuContext.Consumer>
diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts
index 68085c8d1255..b1a900675949 100644
--- a/src/libs/TransactionUtils.ts
+++ b/src/libs/TransactionUtils.ts
@@ -36,10 +36,10 @@ Onyx.connect({
     callback: (value) => (allReports = value),
 });
 
-function isDistanceRequest(transaction: OnyxEntry<Transaction>): boolean {
+function isDistanceRequest(transaction: Transaction): boolean {
     // This is used during the request creation flow before the transaction has been saved to the server
     if (lodashHas(transaction, 'iouRequestType')) {
-        return transaction?.iouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE;
+        return transaction.iouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE;
     }
 
     // This is the case for transaction objects once they have been saved to the server
diff --git a/src/pages/ReportAvatar.tsx b/src/pages/ReportAvatar.tsx
index c61a0a748f7d..2a8114da744d 100644
--- a/src/pages/ReportAvatar.tsx
+++ b/src/pages/ReportAvatar.tsx
@@ -28,6 +28,7 @@ function ReportAvatar({report = {} as Report, policies, isLoadingApp = true}: Re
 
     return (
         <AttachmentModal
+            // @ts-expect-error TODO: Remove this once AttachmentModal (https://github.com/Expensify/App/issues/25130) is migrated to TypeScript.
             headerTitle={policyName}
             defaultOpen
             source={UserUtils.getFullSizeAvatar(avatarURL, 0)}
@@ -37,7 +38,7 @@ function ReportAvatar({report = {} as Report, policies, isLoadingApp = true}: Re
             isWorkspaceAvatar
             originalFileName={policy?.originalFileName ?? policyName}
             shouldShowNotFoundPage={!report?.reportID && !isLoadingApp}
-            isLoading={(!report?.reportID || !policy?.id) && !!isLoadingApp}
+            isLoading={(!report?.reportID || !policy?.id) && isLoadingApp}
         />
     );
 }
diff --git a/src/pages/home/report/ReportAttachments.tsx b/src/pages/home/report/ReportAttachments.tsx
index 2c6ce7a2dabd..42ca51cabe0b 100644
--- a/src/pages/home/report/ReportAttachments.tsx
+++ b/src/pages/home/report/ReportAttachments.tsx
@@ -1,6 +1,5 @@
 import type {StackScreenProps} from '@react-navigation/stack';
 import React, {useCallback} from 'react';
-import type {Attachment} from '@components/AttachmentModal';
 import AttachmentModal from '@components/AttachmentModal';
 import ComposerFocusManager from '@libs/ComposerFocusManager';
 import Navigation from '@libs/Navigation/Navigation';
@@ -9,6 +8,19 @@ import * as ReportUtils from '@libs/ReportUtils';
 import ROUTES from '@src/ROUTES';
 import type SCREENS from '@src/SCREENS';
 
+type File = {
+    name: string;
+};
+
+type Attachment = {
+    file: File;
+    hasBeenFlagged: boolean;
+    isAuthTokenRequired: boolean;
+    isReceipt: boolean;
+    reportActionID: string;
+    source: string;
+};
+
 type ReportAttachmentsProps = StackScreenProps<AuthScreensParamList, typeof SCREENS.REPORT_ATTACHMENTS>;
 
 function ReportAttachments({route}: ReportAttachmentsProps) {
@@ -21,7 +33,7 @@ function ReportAttachments({route}: ReportAttachmentsProps) {
 
     const onCarouselAttachmentChange = useCallback(
         (attachment: Attachment) => {
-            const routeToNavigate = ROUTES.REPORT_ATTACHMENTS.getRoute(reportID, String(attachment.source));
+            const routeToNavigate = ROUTES.REPORT_ATTACHMENTS.getRoute(reportID, attachment.source);
             Navigation.navigate(routeToNavigate);
         },
         [reportID],
@@ -29,6 +41,7 @@ function ReportAttachments({route}: ReportAttachmentsProps) {
 
     return (
         <AttachmentModal
+            // @ts-expect-error TODO: Remove this once AttachmentModal (https://github.com/Expensify/App/issues/25130) is migrated to TypeScript.
             allowDownload
             defaultOpen
             report={report}
diff --git a/src/pages/settings/Profile/ProfileAvatar.tsx b/src/pages/settings/Profile/ProfileAvatar.tsx
index aa39b8eb761f..2aa0f52609c1 100644
--- a/src/pages/settings/Profile/ProfileAvatar.tsx
+++ b/src/pages/settings/Profile/ProfileAvatar.tsx
@@ -34,6 +34,7 @@ function ProfileAvatar({route, personalDetails, isLoadingApp = true}: ProfileAva
 
     return (
         <AttachmentModal
+            // @ts-expect-error TODO: Remove this once AttachmentModal (https://github.com/Expensify/App/issues/25130) is migrated to TypeScript.
             headerTitle={personalDetail?.displayName ?? ''}
             defaultOpen
             source={UserUtils.getFullSizeAvatar(avatarURL, accountID)}
@@ -41,7 +42,7 @@ function ProfileAvatar({route, personalDetails, isLoadingApp = true}: ProfileAva
                 Navigation.goBack();
             }}
             originalFileName={personalDetail?.originalFileName ?? ''}
-            isLoading={!!isLoading}
+            isLoading={isLoading}
             shouldShowNotFoundPage={!avatarURL}
         />
     );
diff --git a/src/pages/workspace/WorkspaceAvatar.tsx b/src/pages/workspace/WorkspaceAvatar.tsx
index 4215d517878b..1a420ee0fbd3 100644
--- a/src/pages/workspace/WorkspaceAvatar.tsx
+++ b/src/pages/workspace/WorkspaceAvatar.tsx
@@ -24,6 +24,7 @@ function WorkspaceAvatar({route, policy, isLoadingApp = true}: WorkspaceAvatarPr
 
     return (
         <AttachmentModal
+            // @ts-expect-error TODO: Remove this once AttachmentModal (https://github.com/Expensify/App/issues/25130) is migrated to TypeScript.
             headerTitle={policy?.name ?? ''}
             defaultOpen
             source={UserUtils.getFullSizeAvatar(avatarURL, 0)}
@@ -33,7 +34,7 @@ function WorkspaceAvatar({route, policy, isLoadingApp = true}: WorkspaceAvatarPr
             isWorkspaceAvatar
             originalFileName={policy?.originalFileName ?? policy?.name ?? ''}
             shouldShowNotFoundPage={!Object.keys(policy ?? {}).length && !isLoadingApp}
-            isLoading={!Object.keys(policy ?? {}).length && !!isLoadingApp}
+            isLoading={!Object.keys(policy ?? {}).length && isLoadingApp}
         />
     );
 }
diff --git a/src/styles/utils/generators/ModalStyleUtils.ts b/src/styles/utils/generators/ModalStyleUtils.ts
index 335ef8382941..3dfb1e67e038 100644
--- a/src/styles/utils/generators/ModalStyleUtils.ts
+++ b/src/styles/utils/generators/ModalStyleUtils.ts
@@ -1,9 +1,9 @@
 import type {ViewStyle} from 'react-native';
 import type {ModalProps} from 'react-native-modal';
+import type {ValueOf} from 'type-fest';
 import type {ThemeStyles} from '@styles/index';
 import variables from '@styles/variables';
 import CONST from '@src/CONST';
-import type ModalType from '@src/types/utils/ModalType';
 import type StyleUtilGenerator from './types';
 
 function getCenteredModalStyles(styles: ThemeStyles, windowWidth: number, isSmallScreenWidth: boolean, isFullScreenWhenSmall = false): ViewStyle {
@@ -15,6 +15,8 @@ function getCenteredModalStyles(styles: ThemeStyles, windowWidth: number, isSmal
     };
 }
 
+type ModalType = ValueOf<typeof CONST.MODAL.MODAL_TYPE>;
+
 type WindowDimensions = {
     windowWidth: number;
     windowHeight: number;
diff --git a/src/types/utils/ModalType.ts b/src/types/utils/ModalType.ts
deleted file mode 100644
index aced9ec35254..000000000000
--- a/src/types/utils/ModalType.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import type {ValueOf} from 'type-fest';
-import type CONST from '@src/CONST';
-
-type ModalType = ValueOf<typeof CONST.MODAL.MODAL_TYPE>;
-
-export default ModalType;
