diff --git a/docs/articles/expensify-classic/expenses/Distance-Tracking.md b/docs/articles/expensify-classic/expenses/Distance-Tracking.md deleted file mode 100644 index c0d8956f71ac..000000000000 --- a/docs/articles/expensify-classic/expenses/Distance-Tracking.md +++ /dev/null @@ -1,81 +0,0 @@ ---- -title: Distance Tracking in Expensify -description: Learn how distance tracking works in Expensify! ---- - -# Overview - -Expensify provides a convenient feature for tracking your mileage-related expenses. You'll find all the essential information to begin logging your trips below. - -# How to Use Distance Tracking -## Mobile App - -First, you’ll want to click the **+** in the top right corner. - -If you select **Manually Create**, you’ll be prompted to enter your mileage, select a rate, and code the expense before clicking **Save**. - - ![Click manually create or odometer to create a distance request.](https://help.expensify.com/assets/images/ExpensifyHelp_CreateExpense_Mobile.png){:width="100%"} - -If you select **Manually Create**: - - Enter your mileage. - - Select a rate. - - Code the expense. - - Click **Save**. - -![Enter your mileage, rate, code the expense, and click save.](https://help.expensify.com/assets/images/ExpensifyHelp_ManualDistance_Mobile.png){:width="100%"} - -If you select **Odometer**: - - Enter your vehicle’s mileage reading before and after your trip. - - Select your rate. - - Code the expense. - - Click **Save**. - -![Etner your mileage readings, your rate, code the expense, and click save.](https://help.expensify.com/assets/images/ExpensifyHelp_Odometer_Mobile.png){:width="100%"} - -The **Start GPS** option also exists on the mobile app. However, we’ve learned that most customers prefer to track their mileage after their trips (thus not needing to hit that start button!) - -We’ve temporarily paused the development of GPS mileage tracking in the mobile app, and we recommend you use one of the above options instead! - - -## Web - -Navigate to the **Expenses** page, click **New Expense**, and review the two **Distance** options. - -![Select manually create or create from map to create a new distance request.](https://help.expensify.com/assets/images/ExpensifyHelp_CreateExpense.png){:width="100%"} - -If you select **Manually Create**: - - Enter the number of miles for your trip. - - Mileage rate is automatically selected based on your history, or manually select it if it's your first time. - - Complete any other applicable coding fields. - - Click **Save**. - -![Enter the number of miles, select your rate, code the expense, and click save.](https://help.expensify.com/assets/images/ExpensifyHelp_ManualDistance.png){:width="100%"} - -For **Create from Map** expenses: - - Add your start and end location, and the distance will be calculated. - - You can also click **Add Destination** for multiple stops. - - Leave **Create Receipt** selected if you want a map receipt generated. - - Click **Save**. - -![Enter your start and end locations, and click save.](https://help.expensify.com/assets/images/ExpensifyHelp_ManualDistanceMap.png){:width="100%"} - -Once you click **Save**, review the details from your map selection. - - Select your rate. - - Enter any other applicable coding. - - Click **Save**. - -![Select your rate, code the expense, and click save.](https://help.expensify.com/assets/images/ExpensifyHelp_ManualDistanceConfirm.png){:width="100%"} - -# Mileage Tracking FAQs -## **How can I change the rate of my mileage expenses?** -You can change the rate by going to Settings > Workspaces > [Your Workspace] > Expenses > Distance > Add a Mileage Rate. -If you submit mileage expenses on a group workspace, only workspace admins can do this. - -## **Do you plan to add the "Create from Map" option to the mobile app or "Odometer" option to web?** -Not now, but if that changes, you'll be the first to know! - -## **Will you restart maintenance on the mobile app's GPS option anytime soon?** -Not now, but if that changes, you'll be the first to know! - -## **Does Expensify automatically update IRS Mileage rates?** - We never automatically update mileage rates in Expensify because different companies want the new rates to become effective on different dates. diff --git a/docs/articles/expensify-classic/expenses/Track-mileage-expenses.md b/docs/articles/expensify-classic/expenses/Track-mileage-expenses.md new file mode 100644 index 000000000000..e8b9ab0eac75 --- /dev/null +++ b/docs/articles/expensify-classic/expenses/Track-mileage-expenses.md @@ -0,0 +1,66 @@ +--- +title: Track mileage expenses +description: Add mileage-related expenses +--- + +
+ +You can track your mileage-related expenses by logging your trips in Expensify. You have a couple of different options for logging distance: + +- Web app: + - **Manually create**: Manually enter the number of miles for the trip + - **Create from map**: Automatically determine the trip distance based on the start and end location. +- Mobile app: + - **Manually create**: Manually enter the miles for the trip and your mileage rate + - **Odometer**: Enter your odometer reading before and after the trip + - **Start GPS**: Currently under development and unavailable for use. + +{% include info.html %} +When adding a distance expense, the rates available are determined by the rates set in your workspace rate settings. To update these rates or add a new rate, you must be a Workspace Admin. +{% include end-info.html %} + +{% include selector.html values="desktop, mobile" %} + +{% include option.html value="desktop" %} + +1. Click the **Expenses** tab. +2. Click **New Expense**. +3. Select the expense type. + - **Manually create**: + - Enter the number of miles for the trip. + - Select your rate. + - If desired, select the category, add a description, or select a report to add the expense to. + - Click **Save**. + - **Create from map**: + - Add your start location as point A. + - Add your end location as point B. + - If applicable, click **Add Destination** to add additional stops. + - To generate a map receipt, leave the Create Receipt checkbox selected. + - Click **Save**. + - Select your rate. + - If desired, select the category, add a description, or select a report to add the expense to. + - Click **Save**. + +{% include end-option.html %} + +{% include option.html value="mobile" %} + +1. Click the + icon in the top right corner. +2. Under the Distance section, select the expense type. + - **Manually create**: + - Enter your mileage. + - Select your rate. + - If desired, click **More Options** to select the category, add a description, or select a report to add the expense to. + - Click **Save**. + - **Odometer**: + - Enter your vehicle’s odometer reading before the trip. + - Enter your vehicle’s odometer reading after the trip. + - Select your rate. + - If desired, click **More Options** to select the category, add a description, or select a report to add the expense to. + - Click **Save**. +{% include end-option.html %} + +{% include end-selector.html %} + +
+ diff --git a/docs/articles/expensify-classic/settings/Copilot.md b/docs/articles/expensify-classic/settings/Copilot.md deleted file mode 100644 index 31bc0eff60e6..000000000000 --- a/docs/articles/expensify-classic/settings/Copilot.md +++ /dev/null @@ -1,71 +0,0 @@ ---- -title: Copilot -description: Safely delegate tasks without sharing login information. ---- - -# About -The Copilot feature allows you to safely delegate tasks without sharing login information. Your chosen user can access your account through their own Expensify account, with customizable permissions to manage expenses, create reports, and more. This can even be extended to users outside your policy or domain. - -# How-to -# How to add a Copilot -1. Log into the Expensify desktop website. -2. Navigate to *Settings > Account > Account Details > _Copilot: Delegated Access_*. -3. Enter the email address or phone number of your Copilot and select whether you want to give them Full Access or the ability to Submit Only. - - *Full Access Copilot*: Your Copilot will have full access to your account. Nearly every action you can do and everything you can see in your account will also be available to your Copilot. They *will not* have the ability to add or remove other Copilots from your account. - - *Submit Only Copilot*: Your Copilot will have the same limitations as a Full Access Copilot, with the added restriction of not being able to approve reports on your behalf. -4. Click Invite Copilot. - -If your Copilot already has an Expensify account, they will get an email notifying them that they can now access your account from within their account as well. -If they do not already have an Expensify account, they will be provided with a link to create one. Once they have created their Expensify account, they will be able to access your account from within their own account. - -# How to use Copilot -A designated copilot can access another account via the Expensify website or the mobile app. - -## How to switch to Copilot mode (on the Expensify website): -1. Click your profile icon in the upper left side of the page. -2. In the “Copilot Access” section of the dropdown, choose the account you wish to access. -3. When you Copilot into someone else’s account, the Expensify header will change color and an airplane icon will appear. -4. You can return to your own account at any time by accessing the user menu and choosing “Return to your account”. - -## How to switch to Copilot Mode (on the mobile app): -1. Tap on the menu icon on the top left-hand side of the screen, then tap your profile icon. -2. Tap “Switch to Copilot Mode”, then choose the account you wish to access. -3. You can return to your own account at any time by accessing the user menu and choosing “Return to your account”. - -# How to remove a Copilot -If you ever need to remove a Copilot, you can do so by following the below steps: -1. Log into the Expensify desktop website -2. Navigate to *Settings > Your Account > Account Details > _Copilot: Delegated Access_* -3. Click the red X next to the Copilot you'd like to remove - - -# Deep Dive -## Copilot Permissions -A Copilot can do the following actions in your account: -- Prepare expenses on your behalf -- Approve and reimburse others' expenses on your behalf (Note: this applies only to **Full Access** Copilots) -- View and make changes to your account/domain/policy settings -- View all expenses you can see within your own account - -## Copilot restrictions -A Copilot cannot do the following actions in your account: -- Change or reset your password -- Add/remove other Copilots - -## Forwarding receipts to receipts@expensify.com as a Copilot -To ensure a receipt is routed to the Expensify account in which you are a copilot rather than your own you’ll need to do the following: -1. Forward the email to receipts@expensify.com -2. Put the email of the account in which you are a copilot in the subject line -3. Send - - -{% include faq-begin.md %} -## Can a Copilot's Secondary Login be used to forward receipts? -Yes! A Copilot can use any of the email addresses tied to their account to forward receipts into the account of the person they're assisting. - -## I'm in Copilot mode for an account; Can I add another Copilot to that account on their behalf? -No, only the original account holder can add another Copilot to the account. -## Is there a restriction on the number of Copilots I can have or the number of users for whom I can act as a Copilot? -There is no limit! You can have as many Copilots as you like, and you can be a Copilot for as many users as you need. - -{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/workspaces/Change-member-workspace-roles.md b/docs/articles/expensify-classic/workspaces/Change-member-workspace-roles.md new file mode 100644 index 000000000000..29fbc8b46323 --- /dev/null +++ b/docs/articles/expensify-classic/workspaces/Change-member-workspace-roles.md @@ -0,0 +1,26 @@ +--- +title: Change member workspace roles +description: Update a member's role for a workspace +--- +
+ +To change the roles and permissions for members of your workspace, + +1. Hover over Settings, then click **Workspaces**. +2. Click the **Group** tab on the left. +3. Click the desired workspace name. +4. Click the **Members** tab on the left. +5. Click the Settings icon next to the desired member. +6. Select a new role for the member. + +| | Employee | Auditor | Workspace Admin | +|---------------------------|----------------------------------|---------|-----------------| +| Submit reports | Yes | Yes | Yes | +| Comment on reports | Yes | Yes | Yes | +| Approve workspace reports | Only reports submitted to them | Yes | Yes | +| Edit workspace settings | No | No | Yes | + +7. If your workspace uses Advanced Approvals, select an “Approves to.” This determines who the member’s reports must be approved by, if applicable. If “no one” is selected, then any one with the Auditor or Workspace Admin role can approve the member’s reports. +8. Click **Save**. + +
diff --git a/src/CONST.ts b/src/CONST.ts index d13c3b374f5a..12c254736cdb 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -335,6 +335,7 @@ const CONST = { TRACK_EXPENSE: 'trackExpense', P2P_DISTANCE_REQUESTS: 'p2pDistanceRequests', WORKFLOWS_DELAYED_SUBMISSION: 'workflowsDelayedSubmission', + ACCOUNTING: 'accounting', }, BUTTON_STATES: { DEFAULT: 'default', diff --git a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx index d389ac4b92f0..da8e3694a7d2 100644 --- a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx +++ b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx @@ -59,7 +59,6 @@ function BaseAnchorForAttachmentsOnly({style, source = '', displayName = '', dow role={CONST.ROLE.BUTTON} > ; }; -type Attachment = { - source: AvatarSource; - isAuthTokenRequired: boolean; - file: FileObject; - isReceipt: boolean; - hasBeenFlagged?: boolean; - reportActionID?: string; -}; - type ImagePickerResponse = { height: number; name: string; @@ -79,7 +71,7 @@ type ImagePickerResponse = { width: number; }; -type FileObject = File | ImagePickerResponse; +type FileObject = Partial; type ChildrenProps = { displayFileInModal: (data: FileObject) => void; @@ -181,7 +173,7 @@ function AttachmentModal({ const [isAuthTokenRequiredState, setIsAuthTokenRequiredState] = useState(isAuthTokenRequired); const [attachmentInvalidReasonTitle, setAttachmentInvalidReasonTitle] = useState(null); const [attachmentInvalidReason, setAttachmentInvalidReason] = useState(null); - const [sourceState, setSourceState] = useState(() => source); + const [sourceState, setSourceState] = useState(() => source); const [modalType, setModalType] = useState(CONST.MODAL.MODAL_TYPE.CENTERED_UNSWIPEABLE); const [isConfirmButtonDisabled, setIsConfirmButtonDisabled] = useState(false); const [confirmButtonFadeAnimation] = useState(() => new Animated.Value(1)); @@ -190,7 +182,7 @@ function AttachmentModal({ const {windowWidth, isSmallScreenWidth} = useWindowDimensions(); const isOverlayModalVisible = (isReceiptAttachment && isDeleteReceiptConfirmModalVisible) || (!isReceiptAttachment && isAttachmentInvalid); - const [file, setFile] = useState | undefined>( + const [file, setFile] = useState( originalFileName ? { name: originalFileName, @@ -211,7 +203,7 @@ function AttachmentModal({ (attachment: Attachment) => { setSourceState(attachment.source); setFile(attachment.file); - setIsAuthTokenRequiredState(attachment.isAuthTokenRequired); + setIsAuthTokenRequiredState(attachment.isAuthTokenRequired ?? false); onCarouselAttachmentChange(attachment); }, [onCarouselAttachmentChange], @@ -222,7 +214,7 @@ function AttachmentModal({ */ const getModalType = useCallback( (sourceURL: string, fileObject: FileObject) => - sourceURL && (Str.isPDF(sourceURL) || (fileObject && Str.isPDF(fileObject.name || translate('attachmentView.unknownFilename')))) + sourceURL && (Str.isPDF(sourceURL) || (fileObject && Str.isPDF(fileObject.name ?? translate('attachmentView.unknownFilename')))) ? CONST.MODAL.MODAL_TYPE.CENTERED_UNSWIPEABLE : CONST.MODAL.MODAL_TYPE.CENTERED, [translate], @@ -292,14 +284,14 @@ function AttachmentModal({ }, [transaction, report]); const isValidFile = useCallback((fileObject: FileObject) => { - if (fileObject.size > CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE) { + if (fileObject.size !== undefined && fileObject.size > 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 (fileObject.size !== undefined && fileObject.size < CONST.API_ATTACHMENT_VALIDATIONS.MIN_SIZE) { setIsAttachmentInvalid(true); setAttachmentInvalidReasonTitle('attachmentPicker.attachmentTooSmall'); setAttachmentInvalidReason('attachmentPicker.sizeNotMet'); @@ -352,7 +344,7 @@ function AttachmentModal({ setSourceState(inputSource); setFile(updatedFile); setModalType(inputModalType); - } else { + } else if (fileObject.uri) { const inputModalType = getModalType(fileObject.uri, fileObject); setIsModalOpen(true); setSourceState(fileObject.uri); @@ -536,7 +528,6 @@ function AttachmentModal({ onNavigate={onNavigate} onClose={closeModal} source={source} - onToggleKeyboard={updateConfirmButtonVisibility} setDownloadButtonVisibility={setDownloadButtonVisibility} /> ) : ( @@ -546,7 +537,6 @@ function AttachmentModal({ !shouldShowNotFoundPage && ( ({ }, })(memo(AttachmentModal)); -export type {Attachment, FileObject}; +export type {FileObject}; diff --git a/src/components/Attachments/AttachmentCarousel/AttachmentCarouselCellRenderer.js b/src/components/Attachments/AttachmentCarousel/AttachmentCarouselCellRenderer.tsx similarity index 71% rename from src/components/Attachments/AttachmentCarousel/AttachmentCarouselCellRenderer.js rename to src/components/Attachments/AttachmentCarousel/AttachmentCarouselCellRenderer.tsx index f4cbffc0e1e4..839e05c419df 100644 --- a/src/components/Attachments/AttachmentCarousel/AttachmentCarouselCellRenderer.js +++ b/src/components/Attachments/AttachmentCarousel/AttachmentCarouselCellRenderer.tsx @@ -1,19 +1,15 @@ -import PropTypes from 'prop-types'; import React from 'react'; +import type {StyleProp, ViewStyle} from 'react-native'; import {PixelRatio, View} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; -const propTypes = { +type AttachmentCarouselCellRendererProps = { /** Cell Container styles */ - style: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]), + style?: StyleProp; }; -const defaultProps = { - style: [], -}; - -function AttachmentCarouselCellRenderer(props) { +function AttachmentCarouselCellRenderer(props: AttachmentCarouselCellRendererProps) { const styles = useThemeStyles(); const {windowWidth, isSmallScreenWidth} = useWindowDimensions(); const modalStyles = styles.centeredModalStyles(isSmallScreenWidth, true); @@ -28,8 +24,6 @@ function AttachmentCarouselCellRenderer(props) { ); } -AttachmentCarouselCellRenderer.propTypes = propTypes; -AttachmentCarouselCellRenderer.defaultProps = defaultProps; AttachmentCarouselCellRenderer.displayName = 'AttachmentCarouselCellRenderer'; export default React.memo(AttachmentCarouselCellRenderer); diff --git a/src/components/Attachments/AttachmentCarousel/CarouselActions.js b/src/components/Attachments/AttachmentCarousel/CarouselActions.tsx similarity index 73% rename from src/components/Attachments/AttachmentCarousel/CarouselActions.js rename to src/components/Attachments/AttachmentCarousel/CarouselActions.tsx index cf5309222c4e..6138f07809c5 100644 --- a/src/components/Attachments/AttachmentCarousel/CarouselActions.js +++ b/src/components/Attachments/AttachmentCarousel/CarouselActions.tsx @@ -1,25 +1,22 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; import {useEffect} from 'react'; import KeyboardShortcut from '@libs/KeyboardShortcut'; import CONST from '@src/CONST'; -const propTypes = { +type CarouselActionsProps = { /** Callback to cycle through attachments */ - onCycleThroughAttachments: PropTypes.func.isRequired, + onCycleThroughAttachments: (deltaSlide: number) => void; }; -function CarouselActions({onCycleThroughAttachments}) { +function CarouselActions({onCycleThroughAttachments}: CarouselActionsProps) { useEffect(() => { const shortcutLeftConfig = CONST.KEYBOARD_SHORTCUTS.ARROW_LEFT; const unsubscribeLeftKey = KeyboardShortcut.subscribe( shortcutLeftConfig.shortcutKey, - (e) => { - if (lodashGet(e, 'target.blur')) { + (event) => { + if (event?.target instanceof HTMLElement) { // prevents focus from highlighting around the modal - e.target.blur(); + event.target.blur(); } - onCycleThroughAttachments(-1); }, shortcutLeftConfig.descriptionKey, @@ -29,12 +26,11 @@ function CarouselActions({onCycleThroughAttachments}) { const shortcutRightConfig = CONST.KEYBOARD_SHORTCUTS.ARROW_RIGHT; const unsubscribeRightKey = KeyboardShortcut.subscribe( shortcutRightConfig.shortcutKey, - (e) => { - if (lodashGet(e, 'target.blur')) { + (event) => { + if (event?.target instanceof HTMLElement) { // prevents focus from highlighting around the modal - e.target.blur(); + event.target.blur(); } - onCycleThroughAttachments(1); }, shortcutRightConfig.descriptionKey, @@ -50,6 +46,4 @@ function CarouselActions({onCycleThroughAttachments}) { return null; } -CarouselActions.propTypes = propTypes; - export default CarouselActions; diff --git a/src/components/Attachments/AttachmentCarousel/CarouselButtons.js b/src/components/Attachments/AttachmentCarousel/CarouselButtons.tsx similarity index 75% rename from src/components/Attachments/AttachmentCarousel/CarouselButtons.js rename to src/components/Attachments/AttachmentCarousel/CarouselButtons.tsx index a2c5dadb101d..2037ebdab086 100644 --- a/src/components/Attachments/AttachmentCarousel/CarouselButtons.js +++ b/src/components/Attachments/AttachmentCarousel/CarouselButtons.tsx @@ -1,8 +1,6 @@ -import PropTypes from 'prop-types'; import React from 'react'; import {View} from 'react-native'; -import _ from 'underscore'; -import * as AttachmentCarouselViewPropTypes from '@components/Attachments/propTypes'; +import type {Attachment} from '@components/Attachments/types'; import Button from '@components/Button'; import * as Expensicons from '@components/Icon/Expensicons'; import Tooltip from '@components/Tooltip'; @@ -11,36 +9,34 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; -const propTypes = { +type CarouselButtonsProps = { /** Where the arrows should be visible */ - shouldShowArrows: PropTypes.bool.isRequired, + shouldShowArrows: boolean; /** The current page index */ - page: PropTypes.number.isRequired, + page: number; /** The attachments from the carousel */ - attachments: AttachmentCarouselViewPropTypes.attachmentsPropType.isRequired, + attachments: Attachment[]; /** Callback to go one page back */ - onBack: PropTypes.func.isRequired, + onBack: () => void; + /** Callback to go one page forward */ - onForward: PropTypes.func.isRequired, + onForward: () => void; - autoHideArrow: PropTypes.func, - cancelAutoHideArrow: PropTypes.func, -}; + /** Callback for autohiding carousel button arrows */ + autoHideArrow?: () => void; -const defaultProps = { - autoHideArrow: () => {}, - cancelAutoHideArrow: () => {}, + /** Callback for cancelling autohiding of carousel button arrows */ + cancelAutoHideArrow?: () => void; }; -function CarouselButtons({page, attachments, shouldShowArrows, onBack, onForward, cancelAutoHideArrow, autoHideArrow}) { +function CarouselButtons({page, attachments, shouldShowArrows, onBack, onForward, cancelAutoHideArrow, autoHideArrow}: CarouselButtonsProps) { const theme = useTheme(); const styles = useThemeStyles(); const isBackDisabled = page === 0; - const isForwardDisabled = page === _.size(attachments) - 1; - + const isForwardDisabled = page === attachments.length - 1; const {translate} = useLocalize(); const {isSmallScreenWidth} = useWindowDimensions(); @@ -80,8 +76,6 @@ function CarouselButtons({page, attachments, shouldShowArrows, onBack, onForward ) : null; } -CarouselButtons.propTypes = propTypes; -CarouselButtons.defaultProps = defaultProps; CarouselButtons.displayName = 'CarouselButtons'; export default CarouselButtons; diff --git a/src/components/Attachments/AttachmentCarousel/CarouselItem.js b/src/components/Attachments/AttachmentCarousel/CarouselItem.tsx similarity index 65% rename from src/components/Attachments/AttachmentCarousel/CarouselItem.js rename to src/components/Attachments/AttachmentCarousel/CarouselItem.tsx index b2c9fed64467..4988110538fe 100644 --- a/src/components/Attachments/AttachmentCarousel/CarouselItem.js +++ b/src/components/Attachments/AttachmentCarousel/CarouselItem.tsx @@ -1,8 +1,8 @@ -import PropTypes from 'prop-types'; import React, {useContext, useState} from 'react'; +import type {StyleProp, ViewStyle} from 'react-native'; import {View} from 'react-native'; import AttachmentView from '@components/Attachments/AttachmentView'; -import * as AttachmentsPropTypes from '@components/Attachments/propTypes'; +import type {Attachment} from '@components/Attachments/types'; import Button from '@components/Button'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import SafeAreaConsumer from '@components/SafeAreaConsumer'; @@ -12,55 +12,27 @@ import useThemeStyles from '@hooks/useThemeStyles'; import ReportAttachmentsContext from '@pages/home/report/ReportAttachmentsContext'; import CONST from '@src/CONST'; -const propTypes = { +type CarouselItemProps = { /** Attachment required information such as the source and file name */ - item: PropTypes.shape({ - /** Report action ID of the attachment */ - reportActionID: PropTypes.string, - - /** Whether source URL requires authentication */ - isAuthTokenRequired: PropTypes.bool, - - /** URL to full-sized attachment or SVG function */ - source: AttachmentsPropTypes.attachmentSourcePropType.isRequired, - - /** Additional information about the attachment file */ - file: PropTypes.shape({ - /** File name of the attachment */ - name: PropTypes.string.isRequired, - }).isRequired, - - /** Whether the attachment has been flagged */ - hasBeenFlagged: PropTypes.bool, - - /** The id of the transaction related to the attachment */ - transactionID: PropTypes.string, - - duration: PropTypes.number, - }).isRequired, + item: Attachment; /** onPress callback */ - onPress: PropTypes.func, + onPress?: () => void; - isModalHovered: PropTypes.bool, + /** Whether attachment carousel modal is hovered over */ + isModalHovered?: boolean; /** Whether the attachment is currently being viewed in the carousel */ - isFocused: PropTypes.bool.isRequired, -}; - -const defaultProps = { - onPress: undefined, - isModalHovered: false, + isFocused: boolean; }; -function CarouselItem({item, onPress, isFocused, isModalHovered}) { +function CarouselItem({item, onPress, isFocused, isModalHovered}: CarouselItemProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const {isAttachmentHidden} = useContext(ReportAttachmentsContext); - // eslint-disable-next-line es/no-nullish-coalescing-operators - const [isHidden, setIsHidden] = useState(() => isAttachmentHidden(item.reportActionID) ?? item.hasBeenFlagged); + const [isHidden, setIsHidden] = useState(() => (item.reportActionID ? isAttachmentHidden(item.reportActionID) : item.hasBeenFlagged)); - const renderButton = (style) => ( + const renderButton = (style: StyleProp) => (