diff --git a/Mobile-Expensify b/Mobile-Expensify index 2c098502da45..21739bc2201e 160000 --- a/Mobile-Expensify +++ b/Mobile-Expensify @@ -1 +1 @@ -Subproject commit 2c098502da45bc07a21454e3292c009a761b32ac +Subproject commit 21739bc2201e85dc3bbbe98e4105480e5047da22 diff --git a/config/webpack/webpack.common.ts b/config/webpack/webpack.common.ts index d51c31f59d96..50d34c095728 100644 --- a/config/webpack/webpack.common.ts +++ b/config/webpack/webpack.common.ts @@ -167,7 +167,8 @@ const getCommonConfiguration = ({file = '.env', platform = 'web'}: Environment): }), // This allows us to interactively inspect JS bundle contents - ...(process.env.ANALYZE_BUNDLE === 'true' ? [new BundleAnalyzerPlugin()] : []), + new BundleAnalyzerPlugin(), + // ...(process.env.ANALYZE_BUNDLE === 'true' ? [new BundleAnalyzerPlugin()] : []), ], module: { rules: [ @@ -352,6 +353,18 @@ const getCommonConfiguration = ({file = '.env', platform = 'web'}: Environment): name: 'heicTo', chunks: 'all', }, + // ExpensifyIcons chunk - separate chunk loaded eagerly for offline support + expensifyIcons: { + test: /[\\/]src[\\/]components[\\/]Icon[\\/]chunks[\\/]expensify-icons\.chunk\.ts$/, + name: 'expensifyIcons', + chunks: 'all', + }, + // Illustrations chunk - separate chunk loaded eagerly for offline support + illustrations: { + test: /[\\/]src[\\/]components[\\/]Icon[\\/]chunks[\\/]illustrations\.chunk\.ts$/, + name: 'illustrations', + chunks: 'all', + }, // Extract all 3rd party dependencies (~75% of App) to separate js file // This gives a more efficient caching - 3rd party deps don't change as often as main source // When dependencies don't change webpack would produce the same js file (and content hash) diff --git a/src/components/ChangeWorkspaceMenuSectionList.tsx b/src/components/ChangeWorkspaceMenuSectionList.tsx index 2bde7393a33e..25bc3957774d 100644 --- a/src/components/ChangeWorkspaceMenuSectionList.tsx +++ b/src/components/ChangeWorkspaceMenuSectionList.tsx @@ -6,8 +6,9 @@ import convertToLTR from '@libs/convertToLTR'; import variables from '@styles/variables'; import type {TranslationPaths} from '@src/languages/types'; import type IconAsset from '@src/types/utils/IconAsset'; +import {useMemoizedLazyAsset} from '@hooks/useLazyAsset'; import Icon from './Icon'; -import * as Illustrations from './Icon/Illustrations'; +import {loadIllustration} from './Icon/IllustrationLoader'; import RenderHTML from './RenderHTML'; type ChangeWorkspaceMenuSection = { @@ -18,20 +19,22 @@ type ChangeWorkspaceMenuSection = { titleTranslationKey: TranslationPaths; }; -const changeWorkspaceMenuSections: ChangeWorkspaceMenuSection[] = [ - { - icon: Illustrations.FolderOpen, - titleTranslationKey: 'iou.changePolicyEducational.reCategorize', - }, - { - icon: Illustrations.Workflows, - titleTranslationKey: 'iou.changePolicyEducational.workflows', - }, -]; - function ChangeWorkspaceMenuSectionList() { const {translate} = useLocalize(); const styles = useThemeStyles(); + const {asset: FolderOpenIcon} = useMemoizedLazyAsset(() => loadIllustration('FolderOpen')); + const {asset: WorkflowsIcon} = useMemoizedLazyAsset(() => loadIllustration('Workflows')); + + const changeWorkspaceMenuSections: ChangeWorkspaceMenuSection[] = [ + { + icon: FolderOpenIcon, + titleTranslationKey: 'iou.changePolicyEducational.reCategorize', + }, + { + icon: WorkflowsIcon, + titleTranslationKey: 'iou.changePolicyEducational.workflows', + }, + ]; return ( <> diff --git a/src/components/FeatureList.tsx b/src/components/FeatureList.tsx index e68b5c360582..da9144fbb3f3 100644 --- a/src/components/FeatureList.tsx +++ b/src/components/FeatureList.tsx @@ -37,7 +37,7 @@ type FeatureListProps = { menuItems: FeatureListItem[]; /** The illustration to display in the header. Can be an image or a JSON object representing a Lottie animation. */ - illustration: DotLottieAnimation | IconAsset; + illustration: DotLottieAnimation | IconAsset | undefined; /** The style passed to the illustration */ illustrationStyle?: StyleProp; diff --git a/src/components/Icon/ExpensifyIconLoader.ts b/src/components/Icon/ExpensifyIconLoader.ts new file mode 100644 index 000000000000..9b2a660e9b55 --- /dev/null +++ b/src/components/Icon/ExpensifyIconLoader.ts @@ -0,0 +1,67 @@ +import type IconAsset from '@src/types/utils/IconAsset'; + +type ExpensifyIconsChunk = { + getExpensifyIcon: (iconName: string) => unknown; + AVAILABLE_EXPENSIFY_ICONS: string[]; +} & Record; + +type ExpensifyIconName = string; + +let expensifyIconsChunk: ExpensifyIconsChunk | null = null; +let chunkLoadingPromise: Promise | null = null; + +/** + * Load the ExpensifyIcons chunk eagerl + */ +function loadExpensifyIconsChunk(): Promise { + if (expensifyIconsChunk) { + return Promise.resolve(expensifyIconsChunk); + } + + if (chunkLoadingPromise) { + return chunkLoadingPromise; + } + + chunkLoadingPromise = import( + /* webpackChunkName: "expensifyIcons" */ + /* webpackPreload: true */ + './chunks/expensify-icons.chunk' + ) + .then((chunk) => { + const typedChunk = chunk as unknown as ExpensifyIconsChunk; + expensifyIconsChunk = typedChunk; + return typedChunk; + }) + .catch((error) => { + chunkLoadingPromise = null; // Reset on error to allow retry + throw new Error(`Failed to load ExpensifyIcons chunk: ${String(error)}`); + }); + + return chunkLoadingPromise; +} + +/** + * Get an ExpensifyIcon by name from the eagerly loaded chunk + * This function provides immediate access once the chunk is loaded + */ +function loadExpensifyIcon(iconName: ExpensifyIconName): Promise<{default: IconAsset}> { + return loadExpensifyIconsChunk() + .then((chunk) => { + const icon = chunk.getExpensifyIcon(iconName) as IconAsset; + if (!icon) { + throw new Error(`ExpensifyIcon "${iconName}" not found`); + } + return {default: icon}; + }) + .catch((error) => { + // eslint-disable-next-line no-console + console.error(`Failed to load ExpensifyIcon: ${iconName}`, error); + throw error; + }); +} + +export { + loadExpensifyIcon, +}; + +export type {ExpensifyIconName}; diff --git a/src/components/Icon/IllustrationLoader.ts b/src/components/Icon/IllustrationLoader.ts new file mode 100644 index 000000000000..01438aa818a3 --- /dev/null +++ b/src/components/Icon/IllustrationLoader.ts @@ -0,0 +1,73 @@ +import type IconAsset from '@src/types/utils/IconAsset'; + +type IllustrationsChunk = { + getIllustration: (illustrationName: string) => unknown; + AVAILABLE_ILLUSTRATIONS: string[]; +} & Record; + +type IllustrationName = string; + +let illustrationsChunk: IllustrationsChunk | null = null; +let chunkLoadingPromise: Promise | null = null; + +/** + * Load the illustrations chunk eagerly + */ +function loadIllustrationsChunk(): Promise { + if (illustrationsChunk) { + return Promise.resolve(illustrationsChunk); + } + + if (chunkLoadingPromise) { + return chunkLoadingPromise; + } + + chunkLoadingPromise = import( + /* webpackChunkName: "illustrations" */ + /* webpackPreload: true */ + './chunks/illustrations.chunk' + ) + .then((chunk) => { + // eslint-disable-next-line no-console + console.log('Loaded illustrations chunk:', chunk); + // eslint-disable-next-line no-console + console.log('Module keys:', Object.keys(chunk)); + // eslint-disable-next-line no-console + console.log('getIllustration function:', chunk.getIllustration); + const typedChunk = chunk as unknown as IllustrationsChunk; + illustrationsChunk = typedChunk; + return typedChunk; + }) + .catch((error) => { + chunkLoadingPromise = null; // Reset on error to allow retry + throw new Error(`Failed to load Illustrations chunk: ${String(error)}`); + }); + + return chunkLoadingPromise; +} + +/** + * Get an Illustration by name from the eagerly loaded chunk + * This function provides immediate access once the chunk is loaded + */ +function loadIllustration(illustrationName: IllustrationName): Promise<{default: IconAsset}> { + return loadIllustrationsChunk() + .then((chunk) => { + const illustration = chunk.getIllustration(illustrationName) as IconAsset; + if (!illustration) { + throw new Error(`Illustration "${illustrationName}" not found`); + } + return {default: illustration}; // Changed to return {default: illustration} + }) + .catch((error) => { + // eslint-disable-next-line no-console + console.error(`Failed to load Illustration: ${illustrationName}`, error); + throw error; + }); +} + +export { + loadIllustration, +}; + +export type {IllustrationName}; diff --git a/src/components/Icon/Illustrations.ts b/src/components/Icon/Illustrations.ts index ccdaf13299d3..97540c2aa29a 100644 --- a/src/components/Icon/Illustrations.ts +++ b/src/components/Icon/Illustrations.ts @@ -10,7 +10,7 @@ import PlaidCompanyCardDetail from '@assets/images/companyCards/card-plaid.svg'; import StripeCompanyCardDetail from '@assets/images/companyCards/card-stripe.svg'; import VisaCompanyCardDetail from '@assets/images/companyCards/card-visa.svg'; import WellsFargoCompanyCardDetail from '@assets/images/companyCards/card-wellsfargo.svg'; -import CompanyCardsEmptyState from '@assets/images/companyCards/emptystate__card-pos.svg'; +// import CompanyCardsEmptyState from '@assets/images/companyCards/emptystate__card-pos.svg'; import AmexCardCompanyCardDetailLarge from '@assets/images/companyCards/large/card-amex-large.svg'; import BankOfAmericaCompanyCardDetailLarge from '@assets/images/companyCards/large/card-bofa-large.svg'; import BrexCompanyCardDetailLarge from '@assets/images/companyCards/large/card-brex-large.svg'; @@ -27,7 +27,7 @@ import PendingBank from '@assets/images/companyCards/pending-bank.svg'; import CompanyCardsPendingState from '@assets/images/companyCards/pendingstate_laptop-with-hourglass-and-cards.svg'; import VisaCompanyCards from '@assets/images/companyCards/visa.svg'; import EmptyCardState from '@assets/images/emptystate__expensifycard.svg'; -import ExpensifyCardIllustration from '@assets/images/expensifyCard/cardIllustration.svg'; +// import ExpensifyCardIllustration from '@assets/images/expensifyCard/cardIllustration.svg'; import LaptopWithSecondScreenAndHourglass from '@assets/images/laptop-with-second-screen-and-hourglass.svg'; import Abracadabra from '@assets/images/product-illustrations/abracadabra.svg'; import BankArrowPink from '@assets/images/product-illustrations/bank-arrow--pink.svg'; @@ -66,7 +66,7 @@ import SafeBlue from '@assets/images/product-illustrations/safe.svg'; import SmartScan from '@assets/images/product-illustrations/simple-illustration__smartscan.svg'; import TadaBlue from '@assets/images/product-illustrations/tada--blue.svg'; import TadaYellow from '@assets/images/product-illustrations/tada--yellow.svg'; -import TeleScope from '@assets/images/product-illustrations/telescope.svg'; +// import TeleScope from '@assets/images/product-illustrations/telescope.svg'; import ThreeLeggedLaptopWoman from '@assets/images/product-illustrations/three_legged_laptop_woman.svg'; import ToddBehindCloud from '@assets/images/product-illustrations/todd-behind-cloud.svg'; import ToddInCar from '@assets/images/product-illustrations/todd-in-car.svg'; @@ -75,14 +75,14 @@ import RunningTurtle from '@assets/images/running-turtle.svg'; import BigVault from '@assets/images/simple-illustrations/emptystate__big-vault.svg'; import Puzzle from '@assets/images/simple-illustrations/emptystate__puzzlepieces.svg'; import Abacus from '@assets/images/simple-illustrations/simple-illustration__abacus.svg'; -import Accounting from '@assets/images/simple-illustrations/simple-illustration__accounting.svg'; +// import Accounting from '@assets/images/simple-illustrations/simple-illustration__accounting.svg'; import Alert from '@assets/images/simple-illustrations/simple-illustration__alert.svg'; import Approval from '@assets/images/simple-illustrations/simple-illustration__approval.svg'; import BankArrow from '@assets/images/simple-illustrations/simple-illustration__bank-arrow.svg'; import BigRocket from '@assets/images/simple-illustrations/simple-illustration__bigrocket.svg'; import PinkBill from '@assets/images/simple-illustrations/simple-illustration__bill.svg'; import Binoculars from '@assets/images/simple-illustrations/simple-illustration__binoculars.svg'; -import Building from '@assets/images/simple-illustrations/simple-illustration__building.svg'; +// import Building from '@assets/images/simple-illustrations/simple-illustration__building.svg'; import Buildings from '@assets/images/simple-illustrations/simple-illustration__buildings.svg'; import CarIce from '@assets/images/simple-illustrations/simple-illustration__car-ice.svg'; import Car from '@assets/images/simple-illustrations/simple-illustration__car.svg'; @@ -90,47 +90,47 @@ import PinkCar from '@assets/images/simple-illustrations/simple-illustration__ca import ChatBubbles from '@assets/images/simple-illustrations/simple-illustration__chatbubbles.svg'; import CheckmarkCircle from '@assets/images/simple-illustrations/simple-illustration__checkmarkcircle.svg'; import CoffeeMug from '@assets/images/simple-illustrations/simple-illustration__coffeemug.svg'; -import Coins from '@assets/images/simple-illustrations/simple-illustration__coins.svg'; +// import Coins from '@assets/images/simple-illustrations/simple-illustration__coins.svg'; import CommentBubbles from '@assets/images/simple-illustrations/simple-illustration__commentbubbles.svg'; import CommentBubblesBlue from '@assets/images/simple-illustrations/simple-illustration__commentbubbles_blue.svg'; import ConciergeBubble from '@assets/images/simple-illustrations/simple-illustration__concierge-bubble.svg'; import ConciergeNew from '@assets/images/simple-illustrations/simple-illustration__concierge.svg'; -import CreditCardsNew from '@assets/images/simple-illustrations/simple-illustration__credit-cards.svg'; +// import CreditCardsNew from '@assets/images/simple-illustrations/simple-illustration__credit-cards.svg'; import CreditCardEyes from '@assets/images/simple-illustrations/simple-illustration__creditcardeyes.svg'; import CreditCardsNewGreen from '@assets/images/simple-illustrations/simple-illustration__creditcards--green.svg'; import EmailAddress from '@assets/images/simple-illustrations/simple-illustration__email-address.svg'; import EmptyShelves from '@assets/images/simple-illustrations/simple-illustration__empty-shelves.svg'; -import EmptyState from '@assets/images/simple-illustrations/simple-illustration__empty-state.svg'; +// import EmptyState from '@assets/images/simple-illustrations/simple-illustration__empty-state.svg'; import Encryption from '@assets/images/simple-illustrations/simple-illustration__encryption.svg'; import EnvelopeReceipt from '@assets/images/simple-illustrations/simple-illustration__envelopereceipt.svg'; import Filters from '@assets/images/simple-illustrations/simple-illustration__filters.svg'; import Flash from '@assets/images/simple-illustrations/simple-illustration__flash.svg'; -import FolderOpen from '@assets/images/simple-illustrations/simple-illustration__folder-open.svg'; +// import FolderOpen from '@assets/images/simple-illustrations/simple-illustration__folder-open.svg'; import Gears from '@assets/images/simple-illustrations/simple-illustration__gears.svg'; -import HandCard from '@assets/images/simple-illustrations/simple-illustration__handcard.svg'; +// import HandCard from '@assets/images/simple-illustrations/simple-illustration__handcard.svg'; import HandEarth from '@assets/images/simple-illustrations/simple-illustration__handearth.svg'; import HeadSet from '@assets/images/simple-illustrations/simple-illustration__headset.svg'; import HotDogStand from '@assets/images/simple-illustrations/simple-illustration__hotdogstand.svg'; import Hourglass from '@assets/images/simple-illustrations/simple-illustration__hourglass.svg'; import House from '@assets/images/simple-illustrations/simple-illustration__house.svg'; -import InvoiceBlue from '@assets/images/simple-illustrations/simple-illustration__invoice.svg'; +// import InvoiceBlue from '@assets/images/simple-illustrations/simple-illustration__invoice.svg'; import Lightbulb from '@assets/images/simple-illustrations/simple-illustration__lightbulb.svg'; import LockClosed from '@assets/images/simple-illustrations/simple-illustration__lockclosed.svg'; import LockClosedOrange from '@assets/images/simple-illustrations/simple-illustration__lockclosed_orange.svg'; import LockOpen from '@assets/images/simple-illustrations/simple-illustration__lockopen.svg'; import Luggage from '@assets/images/simple-illustrations/simple-illustration__luggage.svg'; -import MagnifyingGlassMoney from '@assets/images/simple-illustrations/simple-illustration__magnifyingglass-money.svg'; +// import MagnifyingGlassMoney from '@assets/images/simple-illustrations/simple-illustration__magnifyingglass-money.svg'; import Mailbox from '@assets/images/simple-illustrations/simple-illustration__mailbox.svg'; import ExpensifyMobileApp from '@assets/images/simple-illustrations/simple-illustration__mobileapp.svg'; -import MoneyReceipts from '@assets/images/simple-illustrations/simple-illustration__money-receipts.svg'; +// import MoneyReceipts from '@assets/images/simple-illustrations/simple-illustration__money-receipts.svg'; import MoneyBadge from '@assets/images/simple-illustrations/simple-illustration__moneybadge.svg'; import MoneyIntoWallet from '@assets/images/simple-illustrations/simple-illustration__moneyintowallet.svg'; -import MoneyWings from '@assets/images/simple-illustrations/simple-illustration__moneywings.svg'; +// import MoneyWings from '@assets/images/simple-illustrations/simple-illustration__moneywings.svg'; import OpenSafe from '@assets/images/simple-illustrations/simple-illustration__opensafe.svg'; import PalmTree from '@assets/images/simple-illustrations/simple-illustration__palmtree.svg'; import PaperAirplane from '@assets/images/simple-illustrations/simple-illustration__paperairplane.svg'; import Pencil from '@assets/images/simple-illustrations/simple-illustration__pencil.svg'; -import PerDiem from '@assets/images/simple-illustrations/simple-illustration__perdiem.svg'; +// import PerDiem from '@assets/images/simple-illustrations/simple-illustration__perdiem.svg'; import PiggyBank from '@assets/images/simple-illustrations/simple-illustration__piggybank.svg'; import Pillow from '@assets/images/simple-illustrations/simple-illustration__pillow.svg'; import Profile from '@assets/images/simple-illustrations/simple-illustration__profile.svg'; @@ -138,11 +138,11 @@ import QRCode from '@assets/images/simple-illustrations/simple-illustration__qr- import RealtimeReport from '@assets/images/simple-illustrations/simple-illustration__realtimereports.svg'; import ReceiptEnvelope from '@assets/images/simple-illustrations/simple-illustration__receipt-envelope.svg'; import ReceiptLocationMarker from '@assets/images/simple-illustrations/simple-illustration__receipt-location-marker.svg'; -import ReceiptWrangler from '@assets/images/simple-illustrations/simple-illustration__receipt-wrangler.svg'; +// import ReceiptWrangler from '@assets/images/simple-illustrations/simple-illustration__receipt-wrangler.svg'; import ReceiptPartners from '@assets/images/simple-illustrations/simple-illustration__receipt.svg'; import ReceiptUpload from '@assets/images/simple-illustrations/simple-illustration__receiptupload.svg'; -import ReportReceipt from '@assets/images/simple-illustrations/simple-illustration__report-receipt.svg'; -import Rules from '@assets/images/simple-illustrations/simple-illustration__rules.svg'; +// import ReportReceipt from '@assets/images/simple-illustrations/simple-illustration__report-receipt.svg'; +// import Rules from '@assets/images/simple-illustrations/simple-illustration__rules.svg'; import SanFrancisco from '@assets/images/simple-illustrations/simple-illustration__sanfrancisco.svg'; import SendMoney from '@assets/images/simple-illustrations/simple-illustration__sendmoney.svg'; import ShieldYellow from '@assets/images/simple-illustrations/simple-illustration__shield.svg'; @@ -151,7 +151,7 @@ import SplitBill from '@assets/images/simple-illustrations/simple-illustration__ import Stopwatch from '@assets/images/simple-illustrations/simple-illustration__stopwatch.svg'; import SubscriptionAnnual from '@assets/images/simple-illustrations/simple-illustration__subscription-annual.svg'; import SubscriptionPPU from '@assets/images/simple-illustrations/simple-illustration__subscription-ppu.svg'; -import Tag from '@assets/images/simple-illustrations/simple-illustration__tag.svg'; +// import Tag from '@assets/images/simple-illustrations/simple-illustration__tag.svg'; import TeachersUnite from '@assets/images/simple-illustrations/simple-illustration__teachers-unite.svg'; import ThumbsDown from '@assets/images/simple-illustrations/simple-illustration__thumbsdown.svg'; import ThumbsUpStars from '@assets/images/simple-illustrations/simple-illustration__thumbsupstars.svg'; @@ -159,10 +159,10 @@ import Tire from '@assets/images/simple-illustrations/simple-illustration__tire. import TrackShoe from '@assets/images/simple-illustrations/simple-illustration__track-shoe.svg'; import TrashCan from '@assets/images/simple-illustrations/simple-illustration__trashcan.svg'; import TreasureChest from '@assets/images/simple-illustrations/simple-illustration__treasurechest.svg'; -import CompanyCard from '@assets/images/simple-illustrations/simple-illustration__twocards-horizontal.svg'; +// import CompanyCard from '@assets/images/simple-illustrations/simple-illustration__twocards-horizontal.svg'; import VirtualCard from '@assets/images/simple-illustrations/simple-illustration__virtualcard.svg'; import WalletAlt from '@assets/images/simple-illustrations/simple-illustration__wallet-alt.svg'; -import Workflows from '@assets/images/simple-illustrations/simple-illustration__workflows.svg'; +// import Workflows from '@assets/images/simple-illustrations/simple-illustration__workflows.svg'; import ExpensifyApprovedLogoLight from '@assets/images/subscription-details__approvedlogo--light.svg'; import ExpensifyApprovedLogo from '@assets/images/subscription-details__approvedlogo.svg'; import TurtleInShell from '@assets/images/turtle-in-shell.svg'; @@ -185,8 +185,8 @@ export { EmptyCardState, EmptyStateExpenses, EnvelopeReceipt, - FolderOpen, - HandCard, + // FolderOpen, + // HandCard, HotDogStand, InvoiceOrange, JewelBoxBlue, @@ -201,7 +201,7 @@ export { MushroomTopHat, ReceiptsSearchYellow, ReceiptYellow, - ReceiptWrangler, + // ReceiptWrangler, RocketBlue, RocketDude, RocketOrange, @@ -215,16 +215,16 @@ export { ToddInCar, GpsTrackOrange, ShieldYellow, - MoneyReceipts, + // MoneyReceipts, PinkBill, - CreditCardsNew, + // CreditCardsNew, CreditCardsNewGreen, - InvoiceBlue, + // InvoiceBlue, LaptopWithSecondScreenAndHourglass, LockOpen, Luggage, MoneyIntoWallet, - MoneyWings, + // MoneyWings, OpenSafe, TrackShoe, BankArrow, @@ -241,7 +241,7 @@ export { CommentBubbles, CommentBubblesBlue, TrashCan, - TeleScope, + // TeleScope, Profile, Puzzle, PalmTree, @@ -254,27 +254,27 @@ export { ReceiptEnvelope, Approval, WalletAlt, - Workflows, + // Workflows, PendingBank, ThreeLeggedLaptopWoman, House, - Building, + // Building, Buildings, Alert, TeachersUnite, Abacus, Binoculars, - CompanyCard, + // CompanyCard, ReceiptUpload, - ExpensifyCardIllustration, + // ExpensifyCardIllustration, SplitBill, PiggyBank, Pillow, - Accounting, + // Accounting, Car, - Coins, + // Coins, Pencil, - Tag, + // Tag, CarIce, ReceiptLocationMarker, Lightbulb, @@ -288,16 +288,16 @@ export { CheckmarkCircle, CreditCardEyes, LockClosedOrange, - EmptyState, + // EmptyState, FolderWithPapers, VirtualCard, Tire, BigVault, Filters, - MagnifyingGlassMoney, - Rules, + // MagnifyingGlassMoney, + // Rules, RunningTurtle, - CompanyCardsEmptyState, + // CompanyCardsEmptyState, AmexCompanyCards, MasterCardCompanyCards, VisaCompanyCards, @@ -313,7 +313,7 @@ export { CitibankCompanyCardDetail, StripeCompanyCardDetail, WellsFargoCompanyCardDetail, - PerDiem, + // PerDiem, AmexCardCompanyCardDetailLarge, BankOfAmericaCompanyCardDetailLarge, BrexCompanyCardDetailLarge, @@ -327,9 +327,9 @@ export { WellsFargoCompanyCardDetailLarge, Flash, ExpensifyMobileApp, - ReportReceipt, ModalHoldOrReject, ThumbsDown, + // ReportReceipt, PlaidCompanyCardDetailLarge, PlaidCompanyCardDetail, ReceiptsStackedOnPin, diff --git a/src/components/Icon/PlaceholderIcon.tsx b/src/components/Icon/PlaceholderIcon.tsx new file mode 100644 index 000000000000..a82c4705cce6 --- /dev/null +++ b/src/components/Icon/PlaceholderIcon.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import type {SvgProps} from 'react-native-svg'; +import Svg, {Circle} from 'react-native-svg'; + +function PlaceholderIcon({width = 20, height = 20, fill = '#68748A', ...props}: SvgProps) { + return ( + + + + ); +} + +PlaceholderIcon.displayName = 'PlaceholderIcon'; + +export default PlaceholderIcon; diff --git a/src/components/Icon/chunks/expensify-icons.chunk.ts b/src/components/Icon/chunks/expensify-icons.chunk.ts new file mode 100644 index 000000000000..6c3b10225abd --- /dev/null +++ b/src/components/Icon/chunks/expensify-icons.chunk.ts @@ -0,0 +1,579 @@ +import AddReaction from '@assets/images/add-reaction.svg'; +import All from '@assets/images/all.svg'; +import Android from '@assets/images/android.svg'; +import Apple from '@assets/images/apple.svg'; +import ArrowCircleClockwise from '@assets/images/arrow-circle-clockwise.svg'; +import ArrowCollapse from '@assets/images/arrow-collapse.svg'; +import ArrowDownLong from '@assets/images/arrow-down-long.svg'; +import ArrowRightLong from '@assets/images/arrow-right-long.svg'; +import ArrowRight from '@assets/images/arrow-right.svg'; +import ArrowSplit from '@assets/images/arrow-split.svg'; +import ArrowUpLong from '@assets/images/arrow-up-long.svg'; +import UpArrow from '@assets/images/arrow-up.svg'; +import ArrowsLeftRight from '@assets/images/arrows-leftright.svg'; +import ArrowsUpDown from '@assets/images/arrows-updown.svg'; +import AttachmentNotFound from '@assets/images/attachment-not-found.svg'; +import AdminRoomAvatar from '@assets/images/avatars/admin-room.svg'; +import AnnounceRoomAvatar from '@assets/images/avatars/announce-room.svg'; +import ConciergeAvatar from '@assets/images/avatars/concierge-avatar.svg'; +import DeletedRoomAvatar from '@assets/images/avatars/deleted-room.svg'; +import DomainRoomAvatar from '@assets/images/avatars/domain-room.svg'; +import FallbackAvatar from '@assets/images/avatars/fallback-avatar.svg'; +import FallbackWorkspaceAvatar from '@assets/images/avatars/fallback-workspace-avatar.svg'; +import NotificationsAvatar from '@assets/images/avatars/notifications-avatar.svg'; +import ActiveRoomAvatar from '@assets/images/avatars/room.svg'; +import BackArrow from '@assets/images/back-left.svg'; +import Bank from '@assets/images/bank.svg'; +import Bed from '@assets/images/bed.svg'; +import Bell from '@assets/images/bell.svg'; +import BellSlash from '@assets/images/bellSlash.svg'; +import Bill from '@assets/images/bill.svg'; +import Binoculars from '@assets/images/binoculars.svg'; +import boltSlash from '@assets/images/bolt-slash.svg'; +import Bolt from '@assets/images/bolt.svg'; +import Bookmark from '@assets/images/bookmark.svg'; +import Box from '@assets/images/box.svg'; +import Briefcase from '@assets/images/briefcase.svg'; +import Bug from '@assets/images/bug.svg'; +import Building from '@assets/images/building.svg'; +import Buildings from '@assets/images/buildings.svg'; +import CalendarSolid from '@assets/images/calendar-solid.svg'; +import Calendar from '@assets/images/calendar.svg'; +import Camera from '@assets/images/camera.svg'; +import CarWithKey from '@assets/images/car-with-key.svg'; +import Car from '@assets/images/car.svg'; +import CaretUpDown from '@assets/images/caret-up-down.svg'; +import Cash from '@assets/images/cash.svg'; +import Chair from '@assets/images/chair.svg'; +import ChatBubbleAdd from '@assets/images/chatbubble-add.svg'; +import ChatBubbleCounter from '@assets/images/chatbubble-counter.svg'; +import ChatBubbleReply from '@assets/images/chatbubble-reply.svg'; +import ChatBubbleSlash from '@assets/images/chatbubble-slash.svg'; +import ChatBubbleUnread from '@assets/images/chatbubble-unread.svg'; +import ChatBubble from '@assets/images/chatbubble.svg'; +import ChatBubbles from '@assets/images/chatbubbles.svg'; +import CheckCircle from '@assets/images/check-circle.svg'; +import CheckSquare from '@assets/images/check-square.svg'; +import Checkbox from '@assets/images/checkbox.svg'; +import CheckmarkCircle from '@assets/images/checkmark-circle.svg'; +import Checkmark from '@assets/images/checkmark.svg'; +import CircularArrowBackwards from '@assets/images/circular-arrow-backwards.svg'; +import Close from '@assets/images/close.svg'; +import ClosedSign from '@assets/images/closed-sign.svg'; +import Coins from '@assets/images/coins.svg'; +import Collapse from '@assets/images/collapse.svg'; +import CommentBubbles from '@assets/images/comment-bubbles.svg'; +import Concierge from '@assets/images/concierge.svg'; +import Connect from '@assets/images/connect.svg'; +import ConnectionComplete from '@assets/images/connection-complete.svg'; +import Copy from '@assets/images/copy.svg'; +import CreditCardExclamation from '@assets/images/credit-card-exclamation.svg'; +import CreditCardHourglass from '@assets/images/credit-card-hourglass.svg'; +import CreditCard from '@assets/images/creditcard.svg'; +import Crosshair from '@assets/images/crosshair.svg'; +import DocumentMerge from '@assets/images/document-merge.svg'; +import DocumentPlus from '@assets/images/document-plus.svg'; +import DocumentSlash from '@assets/images/document-slash.svg'; +import Document from '@assets/images/document.svg'; +import DotIndicatorUnfilled from '@assets/images/dot-indicator-unfilled.svg'; +import DotIndicator from '@assets/images/dot-indicator.svg'; +import DownArrow from '@assets/images/down.svg'; +import Download from '@assets/images/download.svg'; +import DragAndDrop from '@assets/images/drag-and-drop.svg'; +import DragHandles from '@assets/images/drag-handles.svg'; +import Emoji from '@assets/images/emoji.svg'; +import Lightbulb from '@assets/images/emojiCategoryIcons/light-bulb.svg'; +import EmptySquare from '@assets/images/empty-square.svg'; +import EmptyStateAttachReceipt from '@assets/images/empty-state__attach-receipt.svg'; +import EmptyStateRoutePending from '@assets/images/emptystate__routepending.svg'; +import EmptyStateSpyPigeon from '@assets/images/emptystate__spy-pigeon.svg'; +import EReceiptIcon from '@assets/images/eReceiptIcon.svg'; +import Exclamation from '@assets/images/exclamation.svg'; +import Exit from '@assets/images/exit.svg'; +import Expand from '@assets/images/expand.svg'; +import ExpensifyAppIcon from '@assets/images/expensify-app-icon.svg'; +import ExpensifyCard from '@assets/images/expensify-card-icon.svg'; +import ExpensifyFooterLogoVertical from '@assets/images/expensify-footer-logo-vertical.svg'; +import ExpensifyFooterLogo from '@assets/images/expensify-footer-logo.svg'; +import ExpensifyLogoNew from '@assets/images/expensify-logo-new.svg'; +import ExpensifyWordmark from '@assets/images/expensify-wordmark.svg'; +import Export from '@assets/images/export.svg'; +import EyeDisabled from '@assets/images/eye-disabled.svg'; +import Eye from '@assets/images/eye.svg'; +import Feed from '@assets/images/feed.svg'; +import Filter from '@assets/images/filter.svg'; +import Filters from '@assets/images/filters.svg'; +import Flag from '@assets/images/flag.svg'; +import FlagLevelOne from '@assets/images/flag_level_01.svg'; +import FlagLevelTwo from '@assets/images/flag_level_02.svg'; +import FlagLevelThree from '@assets/images/flag_level_03.svg'; +import Folder from '@assets/images/folder.svg'; +import Fullscreen from '@assets/images/fullscreen.svg'; +import GalleryNotFound from '@assets/images/gallery-not-found.svg'; +import Gallery from '@assets/images/gallery.svg'; +import Gear from '@assets/images/gear.svg'; +import Globe from '@assets/images/globe.svg'; +import Hashtag from '@assets/images/hashtag.svg'; +import Heart from '@assets/images/heart.svg'; +import History from '@assets/images/history.svg'; +import Home from '@assets/images/home.svg'; +import Hourglass from '@assets/images/hourglass.svg'; +import ImageCropCircleMask from '@assets/images/image-crop-circle-mask.svg'; +import ImageCropSquareMask from '@assets/images/image-crop-square-mask.svg'; +import Inbox from '@assets/images/inbox.svg'; +import Info from '@assets/images/info.svg'; +import CircleSlash from '@assets/images/integrationicons/circle-slash.svg'; +import MicrosoftDynamicsExport from '@assets/images/integrationicons/export/microsoft-dynamics-icon.svg'; +import NetSuiteExport from '@assets/images/integrationicons/export/netsuite-icon.svg'; +import NetSuiteOracleExport from '@assets/images/integrationicons/export/netsuite-oracle-icon.svg'; +import OracleExport from '@assets/images/integrationicons/export/oracle-icon.svg'; +import QBOExport from '@assets/images/integrationicons/export/qbo-icon.svg'; +import SageIntacctExport from '@assets/images/integrationicons/export/sage-intacct-icon.svg'; +import SapExport from '@assets/images/integrationicons/export/sap-icon.svg'; +import XeroExport from '@assets/images/integrationicons/export/xero-icon.svg'; +import MicrosoftDynamicsSquare from '@assets/images/integrationicons/microsoft-dynamics-icon-square.svg'; +import NetSuiteSquare from '@assets/images/integrationicons/netsuite-icon-square.svg'; +import OracleSquare from '@assets/images/integrationicons/oracle-icon-square.svg'; +import QBDSquare from '@assets/images/integrationicons/qbd-icon-square.svg'; +import QBOCircle from '@assets/images/integrationicons/qbo-icon-circle.svg'; +import QBOSquare from '@assets/images/integrationicons/qbo-icon-square.svg'; +import SageIntacctSquare from '@assets/images/integrationicons/sage-intacct-icon-square.svg'; +import SapSquare from '@assets/images/integrationicons/sap-icon-square.svg'; +import Uber from '@assets/images/integrationicons/uber.svg'; +import XeroCircle from '@assets/images/integrationicons/xero-icon-circle.svg'; +import XeroSquare from '@assets/images/integrationicons/xero-icon-square.svg'; +import InvoiceGeneric from '@assets/images/invoice-generic.svg'; +import Invoice from '@assets/images/invoice.svg'; +import Key from '@assets/images/key.svg'; +import Keyboard from '@assets/images/keyboard.svg'; +import LinkCopy from '@assets/images/link-copy.svg'; +import Link from '@assets/images/link.svg'; +import Location from '@assets/images/location.svg'; +import Lock from '@assets/images/lock.svg'; +import Luggage from '@assets/images/luggage.svg'; +import MagnifyingGlassSpyMouthClosed from '@assets/images/magnifying-glass-spy-mouth-closed.svg'; +import MagnifyingGlass from '@assets/images/magnifying-glass.svg'; +import Mail from '@assets/images/mail.svg'; +import MakeAdmin from '@assets/images/make-admin.svg'; +import Map from '@assets/images/map.svg'; +import Megaphone from '@assets/images/megaphone.svg'; +import Menu from '@assets/images/menu.svg'; +import Meter from '@assets/images/meter.svg'; +import Minus from '@assets/images/minus.svg'; +import MoneyBag from '@assets/images/money-bag.svg'; +import MoneyCircle from '@assets/images/money-circle.svg'; +import MoneyHourglass from '@assets/images/money-hourglass.svg'; +import MoneySearch from '@assets/images/money-search.svg'; +import MoneyWaving from '@assets/images/money-waving.svg'; +import Monitor from '@assets/images/monitor.svg'; +import MultiTag from '@assets/images/multi-tag.svg'; +import Mute from '@assets/images/mute.svg'; +import NewWindow from '@assets/images/new-window.svg'; +import NewWorkspace from '@assets/images/new-workspace.svg'; +import OfflineCloud from '@assets/images/offline-cloud.svg'; +import Offline from '@assets/images/offline.svg'; +import OldDotWireframe from '@assets/images/olddot-wireframe.svg'; +import Paperclip from '@assets/images/paperclip.svg'; +import Pause from '@assets/images/pause.svg'; +import Paycheck from '@assets/images/paycheck.svg'; +import Pencil from '@assets/images/pencil.svg'; +import Phone from '@assets/images/phone.svg'; +import Pin from '@assets/images/pin.svg'; +import Plane from '@assets/images/plane.svg'; +import Play from '@assets/images/play.svg'; +import Plus from '@assets/images/plus.svg'; +import Printer from '@assets/images/printer.svg'; +import Profile from '@assets/images/profile.svg'; +import QrCode from '@assets/images/qrcode.svg'; +import QuestionMark from '@assets/images/question-mark-circle.svg'; +import ReceiptBody from '@assets/images/receipt-body.svg'; +import ReceiptMultiple from '@assets/images/receipt-multiple.svg'; +import ReceiptPlaceholderPlus from '@assets/images/receipt-placeholder-plus.svg'; +import ReceiptPlus from '@assets/images/receipt-plus.svg'; +import ReceiptScan from '@assets/images/receipt-scan.svg'; +import ReceiptSearch from '@assets/images/receipt-search.svg'; +import ReceiptSlash from '@assets/images/receipt-slash.svg'; +import Receipt from '@assets/images/receipt.svg'; +import RemoveMembers from '@assets/images/remove-members.svg'; +import Rotate from '@assets/images/rotate-image.svg'; +import RotateLeft from '@assets/images/rotate-left.svg'; +import Scan from '@assets/images/scan.svg'; +import Send from '@assets/images/send.svg'; +import Shield from '@assets/images/shield.svg'; +import AppleLogo from '@assets/images/signIn/apple-logo.svg'; +import GoogleLogo from '@assets/images/signIn/google-logo.svg'; +import AdvancedApprovalsSquare from '@assets/images/simple-illustrations/advanced-approvals-icon-square.svg'; +import MessageInABottle from '@assets/images/simple-illustrations/simple-illustration__messageinabottle.svg'; +import ReplaceReceipt from '@assets/images/simple-illustrations/simple-illustration__replace-receipt.svg'; +import SmartScan from '@assets/images/simple-illustrations/simple-illustration__smartscan.svg'; +import Facebook from '@assets/images/social-facebook.svg'; +import Instagram from '@assets/images/social-instagram.svg'; +import Linkedin from '@assets/images/social-linkedin.svg'; +import Podcast from '@assets/images/social-podcast.svg'; +import Twitter from '@assets/images/social-twitter.svg'; +import Youtube from '@assets/images/social-youtube.svg'; +import SpreadsheetComputer from '@assets/images/spreadsheet-computer.svg'; +import Star from '@assets/images/Star.svg'; +import Stopwatch from '@assets/images/stopwatch.svg'; +import Suitcase from '@assets/images/suitcase.svg'; +import Sync from '@assets/images/sync.svg'; +import Table from '@assets/images/table.svg'; +import Tag from '@assets/images/tag.svg'; +import Task from '@assets/images/task.svg'; +import Thread from '@assets/images/thread.svg'; +import ThreeDots from '@assets/images/three-dots.svg'; +import ThumbsDown from '@assets/images/thumbs-down.svg'; +import ThumbsUp from '@assets/images/thumbs-up.svg'; +import Train from '@assets/images/train.svg'; +import Transfer from '@assets/images/transfer.svg'; +import Trashcan from '@assets/images/trashcan.svg'; +import TreasureChest from '@assets/images/treasure-chest.svg'; +import Unlock from '@assets/images/unlock.svg'; +import UploadAlt from '@assets/images/upload-alt.svg'; +import Upload from '@assets/images/upload.svg'; +import UserCheck from '@assets/images/user-check.svg'; +import UserEye from '@assets/images/user-eye.svg'; +import UserLock from '@assets/images/user-lock.svg'; +import UserPlus from '@assets/images/user-plus.svg'; +import User from '@assets/images/user.svg'; +import Users from '@assets/images/users.svg'; +import VideoSlash from '@assets/images/video-slash.svg'; +import VolumeHigh from '@assets/images/volume-high.svg'; +import VolumeLow from '@assets/images/volume-low.svg'; +import Wallet from '@assets/images/wallet.svg'; +import Workflows from '@assets/images/workflows.svg'; +import Workspace from '@assets/images/workspace-default-avatar.svg'; +import Wrench from '@assets/images/wrench.svg'; +import Clear from '@assets/images/x-circle.svg'; +import Zoom from '@assets/images/zoom.svg'; + +const Expensicons = { + ReceiptBody, + ActiveRoomAvatar, + AddReaction, + AdminRoomAvatar, + All, + Android, + AnnounceRoomAvatar, + Apple, + AppleLogo, + ArrowSplit, + ArrowCollapse, + ArrowRight, + ArrowRightLong, + ArrowsUpDown, + ArrowsLeftRight, + ArrowUpLong, + ArrowDownLong, + AttachmentNotFound, + Wrench, + BackArrow, + Bank, + CircularArrowBackwards, + Bill, + Bell, + BellSlash, + Binoculars, + Bolt, + Box, + Briefcase, + Bug, + Building, + Buildings, + Calendar, + Camera, + Car, + Cash, + ChatBubble, + ChatBubbles, + Checkbox, + Checkmark, + Chair, + Close, + ClosedSign, + Collapse, + CommentBubbles, + Concierge, + ConciergeAvatar, + Connect, + Crosshair, + ConnectionComplete, + Copy, + CreditCard, + CreditCardHourglass, + CreditCardExclamation, + CircleSlash, + DeletedRoomAvatar, + Document, + DocumentSlash, + DocumentMerge, + DomainRoomAvatar, + DotIndicator, + DotIndicatorUnfilled, + DownArrow, + Download, + DragAndDrop, + DragHandles, + EReceiptIcon, + Emoji, + EmptyStateRoutePending, + EmptyStateAttachReceipt, + Exclamation, + Exit, + ExpensifyAppIcon, + ExpensifyCard, + ExpensifyWordmark, + ExpensifyFooterLogo, + ExpensifyFooterLogoVertical, + Expand, + Export, + Eye, + EyeDisabled, + FallbackAvatar, + FallbackWorkspaceAvatar, + Flag, + FlagLevelOne, + FlagLevelTwo, + FlagLevelThree, + Fullscreen, + Folder, + Tag, + MultiTag, + Coins, + Thread, + Gallery, + Gear, + Globe, + GoogleLogo, + Hashtag, + Heart, + History, + Home, + Hourglass, + Inbox, + ImageCropCircleMask, + ImageCropSquareMask, + Info, + Invoice, + InvoiceGeneric, + Key, + Keyboard, + Link, + LinkCopy, + Location, + Lock, + Luggage, + MagnifyingGlass, + Mail, + MakeAdmin, + Map, + Menu, + Meter, + Megaphone, + MessageInABottle, + MoneyBag, + MoneyCircle, + MoneySearch, + MoneyWaving, + MoneyHourglass, + Monitor, + Mute, + ExpensifyLogoNew, + NewWindow, + NewWorkspace, + NotificationsAvatar, + Offline, + OfflineCloud, + OldDotWireframe, + Paperclip, + Pause, + Paycheck, + Pencil, + Phone, + Pin, + Play, + Plus, + Printer, + Profile, + QBOSquare, + QrCode, + QuestionMark, + TreasureChest, + Receipt, + ReceiptPlaceholderPlus, + ReceiptPlus, + ReceiptScan, + ReceiptSlash, + RemoveMembers, + ReceiptSearch, + ReplaceReceipt, + ReceiptMultiple, + Rotate, + RotateLeft, + Scan, + Send, + Shield, + SmartScan, + Stopwatch, + Suitcase, + Sync, + Task, + ThumbsUp, + ThreeDots, + Transfer, + Trashcan, + Uber, + Unlock, + UpArrow, + Upload, + UploadAlt, + User, + UserCheck, + Users, + VideoSlash, + VolumeHigh, + VolumeLow, + Wallet, + Workflows, + Workspace, + XeroSquare, + IntacctSquare: SageIntacctSquare, + AdvancedApprovalsSquare, + Zoom, + Twitter, + Youtube, + Facebook, + Podcast, + Linkedin, + Instagram, + ChatBubbleAdd, + ChatBubbleSlash, + ChatBubbleUnread, + ChatBubbleReply, + ChatBubbleCounter, + Lightbulb, + Plane, + Bed, + CarWithKey, + DocumentPlus, + Clear, + CheckCircle, + CheckmarkCircle, + NetSuiteSquare, + XeroCircle, + QBOCircle, + MicrosoftDynamicsSquare, + OracleSquare, + SapSquare, + Filters, + CalendarSolid, + Filter, + UserEye, + CaretUpDown, + UserPlus, + Feed, + Table, + SpreadsheetComputer, + Bookmark, + Star, + QBDSquare, + GalleryNotFound, + Train, + boltSlash, + MagnifyingGlassSpyMouthClosed, + EmptySquare, + CheckSquare, + Minus, + ThumbsDown, + UserLock, + EmptyStateSpyPigeon, + MicrosoftDynamicsExport, + NetSuiteExport, + NetSuiteOracleExport, + OracleExport, + QBOExport, + SageIntacctExport, + SapExport, + XeroExport, + ArrowCircleClockwise, +}; + +// Create the ExpensifyIcons object from the imported Expensicons +const ExpensifyIcons = Expensicons; + +/** + * Get an ExpensifyIcon by name + * @param iconName - The name of the icon to retrieve + * @returns The icon component or undefined if not found + */ +function getExpensifyIcon(iconName: string): unknown { + // Direct return for known icons to preserve React component type + switch (iconName) { + case 'Building': + return Building; + case 'CalendarSolid': + return CalendarSolid; + case 'Car': + return Car; + case 'Coins': + return Coins; + case 'CreditCard': + return CreditCard; + case 'Document': + return Document; + case 'ExpensifyAppIcon': + return ExpensifyAppIcon; + case 'ExpensifyCard': + return ExpensifyCard; + case 'Feed': + return Feed; + case 'Folder': + return Folder; + case 'Gear': + return Gear; + case 'InvoiceGeneric': + return InvoiceGeneric; + case 'Receipt': + return Receipt; + case 'Sync': + return Sync; + case 'Tag': + return Tag; + case 'Users': + return Users; + case 'Workflows': + return Workflows; + case 'FallbackWorkspaceAvatar': + return FallbackWorkspaceAvatar; + case 'ImageCropSquareMask': + return ImageCropSquareMask; + case 'QrCode': + return QrCode; + case 'Transfer': + return Transfer; + case 'Trashcan': + return Trashcan; + case 'UserPlus': + return UserPlus; + case 'ThreeDots': + return ThreeDots; + default: + // Fallback to object lookup for any other cases + return (ExpensifyIcons as Record)[iconName]; + } +} + +/** + * Get all available ExpensifyIcon names + * @returns Array of available icon names + */ +const AVAILABLE_EXPENSIFY_ICONS = Object.keys(ExpensifyIcons); + +/** + * Type representing all available ExpensifyIcon names + */ +type ExpensifyIconName = keyof typeof ExpensifyIcons; + +export default ExpensifyIcons; +export {getExpensifyIcon, AVAILABLE_EXPENSIFY_ICONS}; +export type {ExpensifyIconName}; diff --git a/src/components/Icon/chunks/illustrations.chunk.ts b/src/components/Icon/chunks/illustrations.chunk.ts new file mode 100644 index 000000000000..5961c0dd6d3e --- /dev/null +++ b/src/components/Icon/chunks/illustrations.chunk.ts @@ -0,0 +1,136 @@ +// This file contains all the SVG imports for illustrations used in the app + +// Company Cards +import CompanyCardsEmptyState from '@assets/images/companyCards/emptystate__card-pos.svg'; + +// Expensify Card +import ExpensifyCardIllustration from '@assets/images/expensifyCard/cardIllustration.svg'; + +// Product Illustrations +import TeleScope from '@assets/images/product-illustrations/telescope.svg'; + +// Simple Illustrations - Core ones that are actually used +import Accounting from '@assets/images/simple-illustrations/simple-illustration__accounting.svg'; +import Building from '@assets/images/simple-illustrations/simple-illustration__building.svg'; +import Coins from '@assets/images/simple-illustrations/simple-illustration__coins.svg'; +import CreditCardsNew from '@assets/images/simple-illustrations/simple-illustration__credit-cards.svg'; +import FolderOpen from '@assets/images/simple-illustrations/simple-illustration__folder-open.svg'; +import HandCard from '@assets/images/simple-illustrations/simple-illustration__handcard.svg'; +import InvoiceBlue from '@assets/images/simple-illustrations/simple-illustration__invoice.svg'; +import MagnifyingGlassMoney from '@assets/images/simple-illustrations/simple-illustration__magnifyingglass-money.svg'; +import MoneyReceipts from '@assets/images/simple-illustrations/simple-illustration__money-receipts.svg'; +import MoneyWings from '@assets/images/simple-illustrations/simple-illustration__moneywings.svg'; +import PerDiem from '@assets/images/simple-illustrations/simple-illustration__perdiem.svg'; +import ReceiptWrangler from '@assets/images/simple-illustrations/simple-illustration__receipt-wrangler.svg'; +import ReportReceipt from '@assets/images/simple-illustrations/simple-illustration__report-receipt.svg'; +import Rules from '@assets/images/simple-illustrations/simple-illustration__rules.svg'; +import Tag from '@assets/images/simple-illustrations/simple-illustration__tag.svg'; +import CompanyCard from '@assets/images/simple-illustrations/simple-illustration__twocards-horizontal.svg'; +import Workflows from '@assets/images/simple-illustrations/simple-illustration__workflows.svg'; + +// Create the illustrations object with all imported illustrations +const Illustrations = { + // Company Cards + CompanyCardsEmptyState, + + // Expensify Card + ExpensifyCardIllustration, + + // Product Illustrations + TeleScope, + Telescope: TeleScope, // Alias for consistency + + // Simple Illustrations + Accounting, + Building, + Coins, + CreditCardsNew, + FolderOpen, + HandCard, + InvoiceBlue, + MagnifyingGlassMoney, + MoneyReceipts, + MoneyWings, + PerDiem, + ReceiptWrangler, + ReportReceipt, + Rules, + Tag, + CompanyCard, + Workflows, + + // Legacy aliases for compatibility + Car: CompanyCard, // Fallback for Car illustration requests +}; + +/** + * Get an illustration by name + * @param illustrationName - The name of the illustration to retrieve + * @returns The illustration component or undefined if not found + */ +function getIllustration(illustrationName: string): unknown { + // Direct return for known illustrations to preserve React component type + switch (illustrationName) { + case 'Building': + return Building; + case 'FolderOpen': + return FolderOpen; + case 'Accounting': + return Accounting; + case 'CompanyCard': + return CompanyCard; + case 'Workflows': + return Workflows; + case 'InvoiceBlue': + return InvoiceBlue; + case 'Rules': + return Rules; + case 'HandCard': + return HandCard; + case 'Tag': + return Tag; + case 'PerDiem': + return PerDiem; + case 'Coins': + return Coins; + case 'TeleScope': + case 'Telescope': + return TeleScope; + case 'CreditCardsNew': + return CreditCardsNew; + case 'MoneyWings': + return MoneyWings; + case 'MoneyReceipts': + return MoneyReceipts; + case 'ExpensifyCardIllustration': + return ExpensifyCardIllustration; + case 'ReceiptWrangler': + return ReceiptWrangler; + case 'ReportReceipt': + return ReportReceipt; + case 'MagnifyingGlassMoney': + return MagnifyingGlassMoney; + case 'CompanyCardsEmptyState': + return CompanyCardsEmptyState; + case 'Car': // Legacy fallback + return CompanyCard; + default: + // Fallback to object lookup for any other cases + return (Illustrations as Record)[illustrationName]; + } +} + +/** + * Get all available illustration names + * @returns Array of available illustration names + */ +const AVAILABLE_ILLUSTRATIONS = Object.keys(Illustrations); + +/** + * Type representing all available illustration names + */ +type IllustrationName = keyof typeof Illustrations; + +export default Illustrations; +export {getIllustration, AVAILABLE_ILLUSTRATIONS}; +export type {IllustrationName}; \ No newline at end of file diff --git a/src/components/ImageSVG/index.android.tsx b/src/components/ImageSVG/index.android.tsx index 5872cfa0bd3b..badac0eb4f2c 100644 --- a/src/components/ImageSVG/index.android.tsx +++ b/src/components/ImageSVG/index.android.tsx @@ -1,10 +1,10 @@ import {Image} from 'expo-image'; import React, {useEffect} from 'react'; -import type {ImageSourcePropType} from 'react-native'; import type ImageSVGProps from './types'; function ImageSVG({src, width = '100%', height = '100%', fill, contentFit = 'cover', style, onLoadEnd}: ImageSVGProps) { const tintColorProp = fill ? {tintColor: fill} : {}; + const isReactComponent = typeof src === 'function'; // Clear memory cache when unmounting images to avoid memory overload useEffect(() => { @@ -14,6 +14,36 @@ function ImageSVG({src, width = '100%', height = '100%', fill, contentFit = 'cov }; }, []); + // Call onLoadEnd immediately for React components since they don't have a loading state + useEffect(() => { + if (!isReactComponent) { + return; + } + onLoadEnd?.(); + }, [isReactComponent, onLoadEnd]); + + // Check if src is a React component (from dynamic loading) or a static image source + if (isReactComponent) { + // Handle React SVG components (from dynamic loading) + const ImageSvgComponent = src; + const additionalProps: Pick = {}; + + if (fill) { + additionalProps.fill = fill; + } + + return ( + + ); + } + + // Handle static image sources (traditional approach) return ( { + if (!isReactComponent) { + return; + } + onLoadEnd?.(); + }, [isReactComponent, onLoadEnd]); + if (isReactComponent) { + // Handle React SVG components (from dynamic loading) + const ImageSvgComponent = src; + const additionalProps: Pick = {}; + + if (fill) { + additionalProps.fill = fill; + } + + return ( + + ); + } + + // Handle static image sources (traditional approach) return ( loadIllustration('Buildings')); // Collate the list of user-created in-app export templates const customInAppTemplates = useMemo(() => { const policyTemplates = Object.entries(policy?.exportLayouts ?? {}).map(([templateName, layout]) => ({ @@ -1054,7 +1056,7 @@ function MoneyReportHeader({ }, [CONST.REPORT.SECONDARY_ACTIONS.CHANGE_WORKSPACE]: { text: translate('iou.changeWorkspace'), - icon: Expensicons.Buildings, + icon: Buildings, value: CONST.REPORT.SECONDARY_ACTIONS.CHANGE_WORKSPACE, onSelected: () => { if (!moneyRequestReport) { diff --git a/src/components/Navigation/NavigationTabBar/index.tsx b/src/components/Navigation/NavigationTabBar/index.tsx index b11aa5f62af7..64b0df279bd9 100644 --- a/src/components/Navigation/NavigationTabBar/index.tsx +++ b/src/components/Navigation/NavigationTabBar/index.tsx @@ -4,7 +4,9 @@ import {View} from 'react-native'; import type {ValueOf} from 'type-fest'; import HeaderGap from '@components/HeaderGap'; import Icon from '@components/Icon'; -import * as Expensicons from '@components/Icon/Expensicons'; +// import * as Expensicons from '@components/Icon/Expensicons'; +import {loadExpensifyIcon} from '@components/Icon/ExpensifyIconLoader'; +import {useMemoizedLazyAsset} from '@hooks/useLazyAsset'; import ImageSVG from '@components/ImageSVG'; import DebugTabView from '@components/Navigation/DebugTabView'; import {PressableWithFeedback} from '@components/Pressable'; @@ -79,6 +81,10 @@ function NavigationTabBar({selectedTab, isTooltipAllowed = false, isTopLevelBar const params = workspacesTabState?.routes?.at(0)?.params as WorkspaceSplitNavigatorParamList[typeof SCREENS.WORKSPACE.INITIAL]; const {typeMenuSections} = useSearchTypeMenuSections(); const subscriptionPlan = useSubscriptionPlan(); + const {asset: ExpensifyAppIcon} = useMemoizedLazyAsset(() => loadExpensifyIcon('ExpensifyAppIcon')); + const {asset: Inbox} = useMemoizedLazyAsset(() => loadExpensifyIcon('Inbox')); + const {asset: MoneySearch} = useMemoizedLazyAsset(() => loadExpensifyIcon('MoneySearch')); + const {asset: Buildings} = useMemoizedLazyAsset(() => loadExpensifyIcon('Buildings')); const [lastViewedPolicy] = useOnyx( ONYXKEYS.COLLECTION.POLICY, @@ -213,7 +219,7 @@ function NavigationTabBar({selectedTab, isTooltipAllowed = false, isTopLevelBar > {}, @@ -54,6 +55,7 @@ function ThreeDotsMenu({ const [position, setPosition] = useState(); const buttonRef = useRef(null); const {translate} = useLocalize(); + const {asset: ThreeDots} = useMemoizedLazyAsset(() => loadExpensifyIcon('ThreeDots')); const isBehindModal = modal?.willAlertModalBecomeVisible && !modal?.isPopover && !shouldOverlay; const {windowWidth, windowHeight} = useWindowDimensions(); const showPopoverMenu = () => { @@ -153,7 +155,7 @@ function ThreeDotsMenu({ accessibilityLabel={translate(iconTooltip)} > diff --git a/src/hooks/useLazyAsset.ts b/src/hooks/useLazyAsset.ts new file mode 100644 index 000000000000..da1bad58a77e --- /dev/null +++ b/src/hooks/useLazyAsset.ts @@ -0,0 +1,83 @@ +import PlaceholderIcon from '@components/Icon/PlaceholderIcon'; +import {useCallback, useEffect, useMemo, useRef, useState} from 'react'; + +type LazyAssetResult = { + asset: T; + isLoaded?: boolean; + isLoading?: boolean; + hasError?: boolean; +}; + +/** + * Hook for lazy loading any type of asset + */ +function useLazyAsset(importFn: () => Promise<{default: T}>, fallback?: T): LazyAssetResult { + const assetRef = useRef(PlaceholderIcon as T); + const versionRef = useRef(0); + + const [isLoaded, setIsLoaded] = useState(false); + const [isLoading, setIsLoading] = useState(true); + const [hasError, setHasError] = useState(false); + + const memoizedImportFn = useMemo(() => importFn, [importFn]); + + useEffect(() => { + let isMounted = true; + const currentVersion = ++versionRef.current; + + const loadAsset = () => { + setIsLoading(true); + setHasError(false); + + memoizedImportFn() + .then((module) => { + // Check if this is still the latest request and component is mounted + if (!isMounted || currentVersion !== versionRef.current) { + return; + } + assetRef.current = module.default; + setIsLoaded(true); + setIsLoading(false); + }) + .catch(() => { + // Check if this is still the latest request and component is mounted + if (!isMounted || currentVersion !== versionRef.current) { + return; + } + setHasError(true); + setIsLoading(false); + + // Use fallback if available + if (fallback) { + assetRef.current = fallback; + setIsLoaded(true); + } + }); + }; + + loadAsset(); + + return () => { + isMounted = false; + }; + }, [memoizedImportFn, fallback]); + + return { + asset: isLoaded ? assetRef?.current : PlaceholderIcon as T, + isLoaded, + isLoading, + hasError, + } as const; +} + +/** + * Hook that automatically memoizes the import function + * This prevents the need for callers to manually use useCallback + */ +function useMemoizedLazyAsset(importFn: () => Promise<{default: T}>, fallback?: T): LazyAssetResult { + const stableImportFn = useCallback(() => importFn(), [importFn]); + return useLazyAsset(stableImportFn, fallback); +} + +export {useMemoizedLazyAsset, type LazyAssetResult}; +export default useLazyAsset; diff --git a/src/pages/EnablePayments/AddBankAccount/SetupMethod.tsx b/src/pages/EnablePayments/AddBankAccount/SetupMethod.tsx index 88b35f1a368e..128cb5c170af 100644 --- a/src/pages/EnablePayments/AddBankAccount/SetupMethod.tsx +++ b/src/pages/EnablePayments/AddBankAccount/SetupMethod.tsx @@ -2,7 +2,6 @@ import React from 'react'; import {View} from 'react-native'; import Button from '@components/Button'; import * as Expensicons from '@components/Icon/Expensicons'; -import * as Illustrations from '@components/Icon/Illustrations'; import Section from '@components/Section'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; @@ -14,6 +13,8 @@ import * as BankAccounts from '@userActions/BankAccounts'; import * as Link from '@userActions/Link'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import {useMemoizedLazyAsset} from '@hooks/useLazyAsset'; +import {loadIllustration} from '@components/Icon/IllustrationLoader'; const plaidDesktopMessage = getPlaidDesktopMessage(); @@ -21,11 +22,12 @@ function SetupMethod() { const styles = useThemeStyles(); const {translate} = useLocalize(); const [isPlaidDisabled] = useOnyx(ONYXKEYS.IS_PLAID_DISABLED); + const {asset: MoneyWings} = useMemoizedLazyAsset(() => loadIllustration('MoneyWings')); return (
diff --git a/src/pages/OnboardingInterestedFeatures/BaseOnboardingInterestedFeatures.tsx b/src/pages/OnboardingInterestedFeatures/BaseOnboardingInterestedFeatures.tsx index 5cdd900da72b..1c3720c70891 100644 --- a/src/pages/OnboardingInterestedFeatures/BaseOnboardingInterestedFeatures.tsx +++ b/src/pages/OnboardingInterestedFeatures/BaseOnboardingInterestedFeatures.tsx @@ -6,7 +6,6 @@ import CustomStatusBarAndBackgroundContext from '@components/CustomStatusBarAndB import FixedFooter from '@components/FixedFooter'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import Icon from '@components/Icon'; -import * as Illustrations from '@components/Icon/Illustrations'; import {PressableWithoutFeedback} from '@components/Pressable'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; @@ -21,6 +20,8 @@ import usePermissions from '@hooks/usePermissions'; import usePrevious from '@hooks/usePrevious'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; +import {useMemoizedLazyAsset} from '@hooks/useLazyAsset'; +import {loadIllustration} from '@components/Icon/IllustrationLoader'; import {openOldDotLink} from '@libs/actions/Link'; import {createWorkspace, generatePolicyID, updateInterestedFeatures} from '@libs/actions/Policy/Policy'; import {completeOnboarding} from '@libs/actions/Report'; @@ -44,6 +45,16 @@ function BaseOnboardingInterestedFeatures({shouldUseNativeStyles}: BaseOnboardin const {translate} = useLocalize(); const {onboardingMessages} = useOnboardingMessages(); const {setRootStatusBarEnabled} = useContext(CustomStatusBarAndBackgroundContext); + const {asset: FolderOpenIcon} = useMemoizedLazyAsset(() => loadIllustration('FolderOpen')); + const {asset: AccountingIcon} = useMemoizedLazyAsset(() => loadIllustration('Accounting')); + const {asset: CompanyCardIcon} = useMemoizedLazyAsset(() => loadIllustration('CompanyCard')); + const {asset: WorkflowsIcon} = useMemoizedLazyAsset(() => loadIllustration('Workflows')); + const {asset: InvoiceBlueIcon} = useMemoizedLazyAsset(() => loadIllustration('InvoiceBlue')); + const {asset: RulesIcon} = useMemoizedLazyAsset(() => loadIllustration('Rules')); + const {asset: CarIcon} = useMemoizedLazyAsset(() => loadIllustration('Car')); + const {asset: TagIcon} = useMemoizedLazyAsset(() => loadIllustration('Tag')); + const {asset: PerDiemIcon} = useMemoizedLazyAsset(() => loadIllustration('PerDiem')); + const {asset: HandCardIcon} = useMemoizedLazyAsset(() => loadIllustration('HandCard')); // We need to use isSmallScreenWidth, see navigateAfterOnboarding function comment // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth @@ -71,66 +82,66 @@ function BaseOnboardingInterestedFeatures({shouldUseNativeStyles}: BaseOnboardin { id: FEATURE_IDS.CATEGORIES, title: translate('workspace.moreFeatures.categories.title'), - icon: Illustrations.FolderOpen, + icon: FolderOpenIcon, enabledByDefault: true, apiEndpoint: WRITE_COMMANDS.ENABLE_POLICY_CATEGORIES, }, { id: FEATURE_IDS.ACCOUNTING, title: translate('workspace.moreFeatures.connections.title'), - icon: Illustrations.Accounting, + icon: AccountingIcon, enabledByDefault: !!userReportedIntegration, apiEndpoint: WRITE_COMMANDS.ENABLE_POLICY_CONNECTIONS, }, { id: FEATURE_IDS.COMPANY_CARDS, title: translate('workspace.moreFeatures.companyCards.title'), - icon: Illustrations.CompanyCard, + icon: CompanyCardIcon, enabledByDefault: true, apiEndpoint: WRITE_COMMANDS.ENABLE_POLICY_COMPANY_CARDS, }, { id: FEATURE_IDS.WORKFLOWS, title: translate('workspace.moreFeatures.workflows.title'), - icon: Illustrations.Workflows, + icon: WorkflowsIcon, enabledByDefault: true, apiEndpoint: WRITE_COMMANDS.ENABLE_POLICY_WORKFLOWS, }, { id: FEATURE_IDS.INVOICES, title: translate('workspace.moreFeatures.invoices.title'), - icon: Illustrations.InvoiceBlue, + icon: InvoiceBlueIcon, apiEndpoint: WRITE_COMMANDS.ENABLE_POLICY_INVOICING, }, { id: FEATURE_IDS.RULES, title: translate('workspace.moreFeatures.rules.title'), - icon: Illustrations.Rules, + icon: RulesIcon, apiEndpoint: WRITE_COMMANDS.SET_POLICY_RULES_ENABLED, requiresUpdate: true, }, { id: FEATURE_IDS.DISTANCE_RATES, title: translate('workspace.moreFeatures.distanceRates.title'), - icon: Illustrations.Car, + icon: CarIcon, apiEndpoint: WRITE_COMMANDS.ENABLE_POLICY_DISTANCE_RATES, }, { id: FEATURE_IDS.EXPENSIFY_CARD, title: translate('workspace.moreFeatures.expensifyCard.title'), - icon: Illustrations.HandCard, + icon: HandCardIcon, apiEndpoint: WRITE_COMMANDS.ENABLE_POLICY_EXPENSIFY_CARDS, }, { id: FEATURE_IDS.TAGS, title: translate('workspace.moreFeatures.tags.title'), - icon: Illustrations.Tag, + icon: TagIcon, apiEndpoint: WRITE_COMMANDS.ENABLE_POLICY_TAGS, }, { id: FEATURE_IDS.PER_DIEM, title: translate('workspace.moreFeatures.perDiem.title'), - icon: Illustrations.PerDiem, + icon: PerDiemIcon, apiEndpoint: WRITE_COMMANDS.TOGGLE_POLICY_PER_DIEM, requiresUpdate: true, }, diff --git a/src/pages/OnboardingWorkspaceOptional/BaseOnboardingWorkspaceOptional.tsx b/src/pages/OnboardingWorkspaceOptional/BaseOnboardingWorkspaceOptional.tsx index 4b59caa15abd..ba6ea9009411 100644 --- a/src/pages/OnboardingWorkspaceOptional/BaseOnboardingWorkspaceOptional.tsx +++ b/src/pages/OnboardingWorkspaceOptional/BaseOnboardingWorkspaceOptional.tsx @@ -3,7 +3,6 @@ import {View} from 'react-native'; import Button from '@components/Button'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import Icon from '@components/Icon'; -import * as Illustrations from '@components/Icon/Illustrations'; import RenderHTML from '@components/RenderHTML'; import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; @@ -14,6 +13,8 @@ import useOnyx from '@hooks/useOnyx'; import usePermissions from '@hooks/usePermissions'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; +import {useMemoizedLazyAsset} from '@hooks/useLazyAsset'; +import {loadIllustration} from '@components/Icon/IllustrationLoader'; import {navigateAfterOnboardingWithMicrotaskQueue} from '@libs/navigateAfterOnboarding'; import Navigation from '@libs/Navigation/Navigation'; import {completeOnboarding as completeOnboardingReport} from '@userActions/Report'; @@ -48,6 +49,9 @@ function BaseOnboardingWorkspaceOptional({shouldUseNativeStyles}: BaseOnboarding const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const {isBetaEnabled} = usePermissions(); const ICON_SIZE = 48; + const {asset: MoneyReceiptsIcon} = useMemoizedLazyAsset(() => loadIllustration('MoneyReceipts')); + const {asset: TagIcon} = useMemoizedLazyAsset(() => loadIllustration('Tag')); + const {asset: ReportReceiptIcon} = useMemoizedLazyAsset(() => loadIllustration('ReportReceipt')); const processedHelperText = `${translate('onboarding.workspace.price')}`; @@ -55,17 +59,18 @@ function BaseOnboardingWorkspaceOptional({shouldUseNativeStyles}: BaseOnboarding setOnboardingErrorMessage(''); }, []); + const section: Item[] = [ { - icon: Illustrations.MoneyReceipts, + icon: MoneyReceiptsIcon, titleTranslationKey: 'onboarding.workspace.explanationModal.descriptionOne', }, { - icon: Illustrations.Tag, + icon: TagIcon, titleTranslationKey: 'onboarding.workspace.explanationModal.descriptionTwo', }, { - icon: Illustrations.ReportReceipt, + icon: ReportReceiptIcon, titleTranslationKey: 'onboarding.workspace.explanationModal.descriptionThree', }, ]; diff --git a/src/pages/settings/Subscription/SubscriptionPlan/index.tsx b/src/pages/settings/Subscription/SubscriptionPlan/index.tsx index bc3f3076fcaa..ebf2ba79b431 100644 --- a/src/pages/settings/Subscription/SubscriptionPlan/index.tsx +++ b/src/pages/settings/Subscription/SubscriptionPlan/index.tsx @@ -2,7 +2,8 @@ import React, {useState} from 'react'; import {View} from 'react-native'; import Button from '@components/Button'; import Icon from '@components/Icon'; -import * as Illustrations from '@components/Icon/Illustrations'; +import {useMemoizedLazyAsset} from '@hooks/useLazyAsset'; +import {loadIllustration} from '@components/Icon/IllustrationLoader'; import Section from '@components/Section'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; @@ -18,6 +19,7 @@ function SubscriptionPlan() { const styles = useThemeStyles(); const subscriptionPlan = useSubscriptionPlan(); const [isModalVisible, setIsModalVisible] = useState(false); + const {asset: HandCard} = useMemoizedLazyAsset(() => loadIllustration('HandCard')); const renderTitle = () => { return ( @@ -40,7 +42,7 @@ function SubscriptionPlan() { loadIllustration('CreditCardsNew')); useEffect(() => { openSubscriptionPage(); }, []); @@ -58,7 +59,7 @@ function SubscriptionSettingsPage({route}: SubscriptionSettingsPageProps) { }} shouldShowBackButton={shouldUseNarrowLayout} shouldDisplaySearchRouter - icon={Illustrations.CreditCardsNew} + icon={CreditCardsNew} shouldUseHeadlineHeader /> diff --git a/src/pages/settings/Wallet/ExpensifyCardPage.tsx b/src/pages/settings/Wallet/ExpensifyCardPage.tsx index 6434b3454071..d96789545387 100644 --- a/src/pages/settings/Wallet/ExpensifyCardPage.tsx +++ b/src/pages/settings/Wallet/ExpensifyCardPage.tsx @@ -6,7 +6,8 @@ import Button from '@components/Button'; import CardPreview from '@components/CardPreview'; import DotIndicatorMessage from '@components/DotIndicatorMessage'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import * as Expensicons from '@components/Icon/Expensicons'; +import {loadExpensifyIcon} from '@components/Icon/ExpensifyIconLoader'; +import {useMemoizedLazyAsset} from '@hooks/useLazyAsset'; import {LockedAccountContext} from '@components/LockedAccountModalProvider'; import MenuItem from '@components/MenuItem'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; @@ -84,6 +85,8 @@ function ExpensifyCardPage({ const expensifyCardTitle = isTravelCard ? translate('cardPage.expensifyTravelCard') : translate('cardPage.expensifyCard'); const pageTitle = shouldDisplayCardDomain ? expensifyCardTitle : (cardList?.[cardID]?.nameValuePairs?.cardTitle ?? expensifyCardTitle); const {displayName} = useCurrentUserPersonalDetails(); + const {asset: Flag} = useMemoizedLazyAsset(() => loadExpensifyIcon('Flag')); + const {asset: MoneySearch} = useMemoizedLazyAsset(() => loadExpensifyIcon('MoneySearch')); const [isNotFound, setIsNotFound] = useState(false); const cardsToShow = useMemo(() => { @@ -280,7 +283,7 @@ function ExpensifyCardPage({ { if (isAccountLocked) { @@ -331,7 +334,7 @@ function ExpensifyCardPage({ Navigation.navigate(ROUTES.SETTINGS_REPORT_FRAUD.getRoute(String(card.cardID), Navigation.getActiveRoute()))} /> @@ -354,7 +357,7 @@ function ExpensifyCardPage({ /> { if (isAccountLocked) { @@ -367,7 +370,7 @@ function ExpensifyCardPage({ )} { diff --git a/src/pages/settings/Wallet/WalletPage/WalletPage.tsx b/src/pages/settings/Wallet/WalletPage/WalletPage.tsx index 562cebdab32f..097d1078bae4 100644 --- a/src/pages/settings/Wallet/WalletPage/WalletPage.tsx +++ b/src/pages/settings/Wallet/WalletPage/WalletPage.tsx @@ -12,6 +12,9 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import * as Illustrations from '@components/Icon/Illustrations'; +import {useMemoizedLazyAsset} from '@hooks/useLazyAsset'; +import {loadIllustration} from '@components/Icon/IllustrationLoader'; + import KYCWall from '@components/KYCWall'; import type {PaymentMethodType, Source} from '@components/KYCWall/types'; import {LockedAccountContext} from '@components/LockedAccountModalProvider'; @@ -74,6 +77,7 @@ function WalletPage({shouldListenForResize = false}: WalletPageProps) { const {isActingAsDelegate, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext); const {isAccountLocked, showLockedAccountModal} = useContext(LockedAccountContext); const {isBetaEnabled} = usePermissions(); + const {asset: MoneySearch} = useMemoizedLazyAsset(() => loadIllustration('MoneySearch')); const theme = useTheme(); const styles = useThemeStyles(); @@ -761,7 +765,7 @@ function WalletPage({shouldListenForResize = false}: WalletPageProps) { /> )} { hideCardMenu(); diff --git a/src/pages/workspace/WorkspaceInitialPage.tsx b/src/pages/workspace/WorkspaceInitialPage.tsx index d04a4c4aa832..bf9e17e90e2d 100644 --- a/src/pages/workspace/WorkspaceInitialPage.tsx +++ b/src/pages/workspace/WorkspaceInitialPage.tsx @@ -5,25 +5,8 @@ import type {ValueOf} from 'type-fest'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import HighlightableMenuItem from '@components/HighlightableMenuItem'; -import { - Building, - CalendarSolid, - Car, - Coins, - CreditCard, - Document, - ExpensifyAppIcon, - ExpensifyCard, - Feed, - Folder, - Gear, - InvoiceGeneric, - Receipt, - Sync, - Tag, - Users, - Workflows, -} from '@components/Icon/Expensicons'; +import {loadExpensifyIcon} from '@components/Icon/ExpensifyIconLoader'; +import {useMemoizedLazyAsset} from '@hooks/useLazyAsset'; import MenuItem from '@components/MenuItem'; import NavigationTabBar from '@components/Navigation/NavigationTabBar'; import NAVIGATION_TABS from '@components/Navigation/NavigationTabBar/NAVIGATION_TABS'; @@ -106,6 +89,26 @@ function dismissError(policyID: string | undefined, pendingAction: PendingAction function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: WorkspaceInitialPageProps) { const styles = useThemeStyles(); + + // Direct access to ExpensifyIcons from chunk + const {asset: Building} = useMemoizedLazyAsset(() => loadExpensifyIcon('Building')); + const {asset: CalendarSolid} = useMemoizedLazyAsset(() => loadExpensifyIcon('CalendarSolid')); + const {asset: Car} = useMemoizedLazyAsset(() => loadExpensifyIcon('Car')); + const {asset: Coins} = useMemoizedLazyAsset(() => loadExpensifyIcon('Coins')); + const {asset: CreditCard} = useMemoizedLazyAsset(() => loadExpensifyIcon('CreditCard')); + const {asset: Document} = useMemoizedLazyAsset(() => loadExpensifyIcon('Document')); + const {asset: ExpensifyAppIcon} = useMemoizedLazyAsset(() => loadExpensifyIcon('ExpensifyAppIcon')); + const {asset: ExpensifyCard} = useMemoizedLazyAsset(() => loadExpensifyIcon('ExpensifyCard')); + const {asset: Feed} = useMemoizedLazyAsset(() => loadExpensifyIcon('Feed')); + const {asset: Folder} = useMemoizedLazyAsset(() => loadExpensifyIcon('Folder')); + const {asset: Gear} = useMemoizedLazyAsset(() => loadExpensifyIcon('Gear')); + const {asset: InvoiceGeneric} = useMemoizedLazyAsset(() => loadExpensifyIcon('InvoiceGeneric')); + const {asset: Receipt} = useMemoizedLazyAsset(() => loadExpensifyIcon('Receipt')); + const {asset: Sync} = useMemoizedLazyAsset(() => loadExpensifyIcon('Sync')); + const {asset: Tag} = useMemoizedLazyAsset(() => loadExpensifyIcon('Tag')); + const {asset: Users} = useMemoizedLazyAsset(() => loadExpensifyIcon('Users')); + const {asset: Workflows} = useMemoizedLazyAsset(() => loadExpensifyIcon('Workflows')); + const policy = policyDraft?.id ? policyDraft : policyProp; const workspaceAccountID = policy?.workspaceAccountID ?? CONST.DEFAULT_NUMBER_ID; const hasPolicyCreationError = policy?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD && !isEmptyObject(policy.errors); diff --git a/src/pages/workspace/WorkspaceMembersPage.tsx b/src/pages/workspace/WorkspaceMembersPage.tsx index 5f2361cac286..ce61cfcc4237 100644 --- a/src/pages/workspace/WorkspaceMembersPage.tsx +++ b/src/pages/workspace/WorkspaceMembersPage.tsx @@ -10,8 +10,8 @@ import ButtonWithDropdownMenu from '@components/ButtonWithDropdownMenu'; import type {DropdownOption, WorkspaceMemberBulkActionType} from '@components/ButtonWithDropdownMenu/types'; import ConfirmModal from '@components/ConfirmModal'; import DecisionModal from '@components/DecisionModal'; +import {loadIllustration} from '@components/Icon/IllustrationLoader'; import {Download, FallbackAvatar, MakeAdmin, Plus, RemoveMembers, Table, User, UserEye} from '@components/Icon/Expensicons'; -import {ReceiptWrangler} from '@components/Icon/Illustrations'; import {LockedAccountContext} from '@components/LockedAccountModalProvider'; import MessagesRow from '@components/MessagesRow'; import SearchBar from '@components/SearchBar'; @@ -22,6 +22,7 @@ import CustomListHeader from '@components/SelectionListWithModal/CustomListHeade import Text from '@components/Text'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useFilteredSelection from '@hooks/useFilteredSelection'; +import {useMemoizedLazyAsset} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useMobileSelectionMode from '@hooks/useMobileSelectionMode'; import useNetwork from '@hooks/useNetwork'; @@ -143,6 +144,7 @@ function WorkspaceMembersPage({personalDetails, route, policy}: WorkspaceMembers const selectionListRef = useRef(null); const isFocused = useIsFocused(); const policyID = route.params.policyID; + const {asset: ReceiptWrangler} = useMemoizedLazyAsset(() => loadIllustration('ReceiptWrangler')) const ownerDetails = personalDetails?.[policy?.ownerAccountID ?? CONST.DEFAULT_NUMBER_ID] ?? ({} as PersonalDetails); const {approvalWorkflows} = useMemo( diff --git a/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx b/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx index 48d8bdc27cd4..adb15868cafc 100644 --- a/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx +++ b/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx @@ -17,6 +17,8 @@ import usePermissions from '@hooks/usePermissions'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; +import {useMemoizedLazyAsset} from '@hooks/useLazyAsset'; +import {loadIllustration} from '@components/Icon/IllustrationLoader'; import {filterInactiveCards, getAllCardsForWorkspace, getCompanyFeeds, isSmartLimitEnabled as isSmartLimitEnabledUtil} from '@libs/CardUtils'; import {getLatestErrorField} from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; @@ -113,6 +115,16 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro const defaultFundID = useDefaultFundID(policyID); const [cardSettings] = useOnyx(`${ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS}${defaultFundID}`, {canBeMissing: true}); const paymentBankAccountID = cardSettings?.paymentBankAccountID; + const {asset: FolderOpenIcon} = useMemoizedLazyAsset(() => loadIllustration('FolderOpen')); + const {asset: AccountingIcon} = useMemoizedLazyAsset(() => loadIllustration('Accounting')); + const {asset: CompanyCardIcon} = useMemoizedLazyAsset(() => loadIllustration('CompanyCard')); + const {asset: WorkflowsIcon} = useMemoizedLazyAsset(() => loadIllustration('Workflows')); + const {asset: InvoiceBlueIcon} = useMemoizedLazyAsset(() => loadIllustration('InvoiceBlue')); + const {asset: RulesIcon} = useMemoizedLazyAsset(() => loadIllustration('Rules')); + const {asset: HandCardIcon} = useMemoizedLazyAsset(() => loadIllustration('HandCard')); + const {asset: TagIcon} = useMemoizedLazyAsset(() => loadIllustration('Tag')); + const {asset: PerDiemIcon} = useMemoizedLazyAsset(() => loadIllustration('PerDiem')); + const {asset: CoinsIcon} = useMemoizedLazyAsset(() => loadIllustration('Coins')); const onDisabledOrganizeSwitchPress = useCallback(() => { if (!hasAccountingConnection) { @@ -143,7 +155,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro }, }, { - icon: Illustrations.HandCard, + icon: HandCardIcon, titleTranslationKey: 'workspace.moreFeatures.expensifyCard.title', subtitleTranslationKey: 'workspace.moreFeatures.expensifyCard.subtitle', isActive: policy?.areExpensifyCardsEnabled ?? false, @@ -162,7 +174,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro ]; spendItems.push({ - icon: Illustrations.CompanyCard, + icon: CompanyCardIcon, titleTranslationKey: 'workspace.moreFeatures.companyCards.title', subtitleTranslationKey: 'workspace.moreFeatures.companyCards.subtitle', isActive: policy?.areCompanyCardsEnabled ?? false, @@ -180,7 +192,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro }); spendItems.push({ - icon: Illustrations.PerDiem, + icon: PerDiemIcon, titleTranslationKey: 'workspace.moreFeatures.perDiem.title', subtitleTranslationKey: 'workspace.moreFeatures.perDiem.subtitle', isActive: policy?.arePerDiemRatesEnabled ?? false, @@ -199,7 +211,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro const manageItems: Item[] = [ { - icon: Illustrations.Workflows, + icon: WorkflowsIcon, titleTranslationKey: 'workspace.moreFeatures.workflows.title', subtitleTranslationKey: 'workspace.moreFeatures.workflows.subtitle', isActive: policy?.areWorkflowsEnabled ?? false, @@ -214,7 +226,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro disabledAction: onDisabledWorkflowPress, }, { - icon: Illustrations.Rules, + icon: RulesIcon, titleTranslationKey: 'workspace.moreFeatures.rules.title', subtitleTranslationKey: 'workspace.moreFeatures.rules.subtitle', isActive: policy?.areRulesEnabled ?? false, @@ -235,7 +247,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro const earnItems: Item[] = [ { - icon: Illustrations.InvoiceBlue, + icon: InvoiceBlueIcon, titleTranslationKey: 'workspace.moreFeatures.invoices.title', subtitleTranslationKey: 'workspace.moreFeatures.invoices.subtitle', isActive: policy?.areInvoicesEnabled ?? false, @@ -251,7 +263,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro const organizeItems: Item[] = [ { - icon: Illustrations.FolderOpen, + icon: FolderOpenIcon, titleTranslationKey: 'workspace.moreFeatures.categories.title', subtitleTranslationKey: 'workspace.moreFeatures.categories.subtitle', isActive: policy?.areCategoriesEnabled ?? false, @@ -266,7 +278,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro }, }, { - icon: Illustrations.Tag, + icon: TagIcon, titleTranslationKey: 'workspace.moreFeatures.tags.title', subtitleTranslationKey: 'workspace.moreFeatures.tags.subtitle', isActive: policy?.areTagsEnabled ?? false, @@ -281,7 +293,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro }, }, { - icon: Illustrations.Coins, + icon: CoinsIcon, titleTranslationKey: 'workspace.moreFeatures.taxes.title', subtitleTranslationKey: 'workspace.moreFeatures.taxes.subtitle', isActive: (policy?.tax?.trackingEnabled ?? false) || isSyncTaxEnabled, @@ -299,7 +311,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro const integrateItems: Item[] = [ { - icon: Illustrations.Accounting, + icon: AccountingIcon, titleTranslationKey: 'workspace.moreFeatures.connections.title', subtitleTranslationKey: 'workspace.moreFeatures.connections.subtitle', isActive: isAccountingEnabled, diff --git a/src/pages/workspace/WorkspaceNewRoomPage.tsx b/src/pages/workspace/WorkspaceNewRoomPage.tsx index aed3473f6a19..6610299f1548 100644 --- a/src/pages/workspace/WorkspaceNewRoomPage.tsx +++ b/src/pages/workspace/WorkspaceNewRoomPage.tsx @@ -7,13 +7,14 @@ import Button from '@components/Button'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; import type {FormOnyxValues} from '@components/Form/types'; -import * as Illustrations from '@components/Icon/Illustrations'; +import {loadIllustration} from '@components/Icon/IllustrationLoader'; import type {AnimatedTextInputRef} from '@components/RNTextInput'; import RoomNameInput from '@components/RoomNameInput'; import ScreenWrapper from '@components/ScreenWrapper'; import TextInput from '@components/TextInput'; import ValuePicker from '@components/ValuePicker'; import useBottomSafeSafeAreaPaddingStyle from '@hooks/useBottomSafeSafeAreaPaddingStyle'; +import {useMemoizedLazyAsset} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import useSafeAreaInsets from '@hooks/useSafeAreaInsets'; @@ -35,11 +36,12 @@ function EmptyWorkspaceView() { const styles = useThemeStyles(); const {translate} = useLocalize(); const bottomSafeAreaPaddingStyle = useBottomSafeSafeAreaPaddingStyle({addBottomSafeAreaPadding: true, additionalPaddingBottom: styles.mb5.marginBottom, styleProperty: 'marginBottom'}); + const {asset: TeleScopeIcon} = useMemoizedLazyAsset(() => loadIllustration('Telescope')); return ( <> loadIllustration('Building')); + + const {asset: FallbackWorkspaceAvatar} = useMemoizedLazyAsset(() => loadExpensifyIcon('FallbackWorkspaceAvatar')); + const {asset: ImageCropSquareMask} = useMemoizedLazyAsset(() => loadExpensifyIcon('ImageCropSquareMask')); + const {asset: QrCode} = useMemoizedLazyAsset(() => loadExpensifyIcon('QrCode')); + const {asset: Transfer} = useMemoizedLazyAsset(() => loadExpensifyIcon('Transfer')); + const {asset: Trashcan} = useMemoizedLazyAsset(() => loadExpensifyIcon('Trashcan')); + const {asset: UserPlus} = useMemoizedLazyAsset(() => loadExpensifyIcon('UserPlus')); + + const backTo = route.params.backTo; const [currencyList = getEmptyObject()] = useOnyx(ONYXKEYS.CURRENCY_LIST, {canBeMissing: true}); const [currentUserAccountID = -1] = useOnyx(ONYXKEYS.SESSION, { @@ -315,7 +326,7 @@ function WorkspaceOverviewPage({policyDraft, policy: policyProp, route}: Workspa shouldUseScrollView shouldShowOfflineIndicatorInWideScreen shouldShowNonAdmin - icon={Building} + icon={BuildingIcon} shouldShowNotFoundPage={policy === undefined} onBackButtonPress={handleBackButtonPress} addBottomSafeAreaPadding diff --git a/src/pages/workspace/accounting/PolicyAccountingPage.tsx b/src/pages/workspace/accounting/PolicyAccountingPage.tsx index 5834c079c7a3..7f7edab63de0 100644 --- a/src/pages/workspace/accounting/PolicyAccountingPage.tsx +++ b/src/pages/workspace/accounting/PolicyAccountingPage.tsx @@ -7,8 +7,8 @@ import ConfirmModal from '@components/ConfirmModal'; import FormHelpMessage from '@components/FormHelpMessage'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import Icon from '@components/Icon'; +import {loadIllustration} from '@components/Icon/IllustrationLoader'; import * as Expensicons from '@components/Icon/Expensicons'; -import * as Illustrations from '@components/Icon/Illustrations'; import MenuItem from '@components/MenuItem'; import MenuItemList from '@components/MenuItemList'; import type {MenuItemWithLink} from '@components/MenuItemList'; @@ -21,6 +21,7 @@ import TextLink from '@components/TextLink'; import ThreeDotsMenu from '@components/ThreeDotsMenu'; import type ThreeDotsMenuProps from '@components/ThreeDotsMenu/types'; import useExpensifyCardFeeds from '@hooks/useExpensifyCardFeeds'; +import {useMemoizedLazyAsset} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; @@ -86,6 +87,7 @@ function PolicyAccountingPage({policy}: PolicyAccountingPageProps) { const policyID = policy?.id; const allCardSettings = useExpensifyCardFeeds(policyID); const isSyncInProgress = isConnectionInProgress(connectionSyncProgress, policy); + const {asset: AccountingIcon} = useMemoizedLazyAsset(() => loadIllustration('Accounting')); const connectionNames = CONST.POLICY.CONNECTIONS.NAME; const accountingIntegrations = Object.values(connectionNames); @@ -508,7 +510,7 @@ function PolicyAccountingPage({policy}: PolicyAccountingPageProps) { diff --git a/src/pages/workspace/accounting/intacct/advanced/SageIntacctPaymentAccountPage.tsx b/src/pages/workspace/accounting/intacct/advanced/SageIntacctPaymentAccountPage.tsx index 5bb6205b8160..927c966c497d 100644 --- a/src/pages/workspace/accounting/intacct/advanced/SageIntacctPaymentAccountPage.tsx +++ b/src/pages/workspace/accounting/intacct/advanced/SageIntacctPaymentAccountPage.tsx @@ -1,6 +1,5 @@ import React, {useCallback, useMemo} from 'react'; import BlockingView from '@components/BlockingViews/BlockingView'; -import * as Illustrations from '@components/Icon/Illustrations'; import RadioListItem from '@components/SelectionList/RadioListItem'; import type {SelectorType} from '@components/SelectionScreen'; import SelectionScreen from '@components/SelectionScreen'; @@ -16,6 +15,8 @@ import {updateSageIntacctSyncReimbursementAccountID} from '@userActions/connecti import * as Policy from '@userActions/Policy/Policy'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; +import { useMemoizedLazyAsset } from '@hooks/useLazyAsset'; +import { loadIllustration } from '@components/Icon/IllustrationLoader'; function SageIntacctPaymentAccountPage({policy}: WithPolicyConnectionsProps) { const styles = useThemeStyles(); @@ -24,6 +25,7 @@ function SageIntacctPaymentAccountPage({policy}: WithPolicyConnectionsProps) { const policyID = policy?.id ?? '-1'; const {config} = policy?.connections?.intacct ?? {}; + const {asset: TeleScope} = useMemoizedLazyAsset(() => loadIllustration('Telescope')); const vendorSelectorOptions = useMemo(() => getSageIntacctBankAccounts(policy, config?.sync?.reimbursementAccountID), [policy, config?.sync?.reimbursementAccountID]); @@ -40,7 +42,7 @@ function SageIntacctPaymentAccountPage({policy}: WithPolicyConnectionsProps) { const listEmptyContent = useMemo( () => ( loadIllustration('Telescope')); const isReimbursable = route.params.reimbursable === CONST.SAGE_INTACCT_CONFIG.REIMBURSABLE; const goBack = useCallback(() => { @@ -91,7 +93,7 @@ function SageIntacctDefaultVendorPage() { containerStyle={styles.pb10} /> ), - [translate, styles.pb10], + [translate, styles.pb10, TeleScope], ); return ( diff --git a/src/pages/workspace/accounting/intacct/export/SageIntacctNonReimbursableCreditCardAccountPage.tsx b/src/pages/workspace/accounting/intacct/export/SageIntacctNonReimbursableCreditCardAccountPage.tsx index f432209c3b83..dfce6900666a 100644 --- a/src/pages/workspace/accounting/intacct/export/SageIntacctNonReimbursableCreditCardAccountPage.tsx +++ b/src/pages/workspace/accounting/intacct/export/SageIntacctNonReimbursableCreditCardAccountPage.tsx @@ -1,7 +1,8 @@ import {useRoute} from '@react-navigation/native'; import React, {useCallback, useMemo} from 'react'; import BlockingView from '@components/BlockingViews/BlockingView'; -import {TeleScope} from '@components/Icon/Illustrations'; +import { useMemoizedLazyAsset } from '@hooks/useLazyAsset'; +import { loadIllustration } from '@components/Icon/IllustrationLoader'; import RadioListItem from '@components/SelectionList/RadioListItem'; import type {SelectorType} from '@components/SelectionScreen'; import SelectionScreen from '@components/SelectionScreen'; @@ -30,6 +31,7 @@ function SageIntacctNonReimbursableCreditCardAccountPage({policy}: WithPolicyCon const {export: exportConfig} = policy?.connections?.intacct?.config ?? {}; const route = useRoute>(); const backTo = route.params.backTo; + const {asset: TeleScope} = useMemoizedLazyAsset(() => loadIllustration('Telescope')); const goBack = useCallback(() => { Navigation.goBack(backTo ?? (policyID && ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_NON_REIMBURSABLE_EXPENSES.getRoute(policyID))); diff --git a/src/pages/workspace/accounting/netsuite/NetSuiteSubsidiarySelector.tsx b/src/pages/workspace/accounting/netsuite/NetSuiteSubsidiarySelector.tsx index f1f0a8c9aca9..db7c93e53466 100644 --- a/src/pages/workspace/accounting/netsuite/NetSuiteSubsidiarySelector.tsx +++ b/src/pages/workspace/accounting/netsuite/NetSuiteSubsidiarySelector.tsx @@ -1,7 +1,8 @@ import React, {useMemo} from 'react'; import {View} from 'react-native'; import BlockingView from '@components/BlockingViews/BlockingView'; -import * as Illustrations from '@components/Icon/Illustrations'; +import { useMemoizedLazyAsset } from '@hooks/useLazyAsset'; +import { loadIllustration } from '@components/Icon/IllustrationLoader'; import RadioListItem from '@components/SelectionList/RadioListItem'; import SelectionScreen from '@components/SelectionScreen'; import type {SelectorType} from '@components/SelectionScreen'; @@ -28,6 +29,8 @@ function NetSuiteSubsidiarySelector({policy}: WithPolicyConnectionsProps) { const currentSubsidiaryID = netsuiteConfig?.subsidiaryID ?? ''; const policyID = policy?.id ?? '-1'; + const {asset: TeleScope} = useMemoizedLazyAsset(() => loadIllustration('Telescope')); + const subsidiaryListSections = subsidiaryList.map((subsidiary: NetSuiteSubsidiary) => ({ text: subsidiary.name, @@ -58,7 +61,7 @@ function NetSuiteSubsidiarySelector({policy}: WithPolicyConnectionsProps) { const listEmptyContent = useMemo( () => ( ), - [translate, styles.pb10], + [translate, styles.pb10, TeleScope], ); const listHeaderComponent = useMemo( diff --git a/src/pages/workspace/accounting/netsuite/advanced/NetSuiteApprovalAccountSelectPage.tsx b/src/pages/workspace/accounting/netsuite/advanced/NetSuiteApprovalAccountSelectPage.tsx index 6b5c4fa4b0cf..49682154f7e0 100644 --- a/src/pages/workspace/accounting/netsuite/advanced/NetSuiteApprovalAccountSelectPage.tsx +++ b/src/pages/workspace/accounting/netsuite/advanced/NetSuiteApprovalAccountSelectPage.tsx @@ -1,7 +1,8 @@ import React, {useCallback, useMemo} from 'react'; import {View} from 'react-native'; import BlockingView from '@components/BlockingViews/BlockingView'; -import * as Illustrations from '@components/Icon/Illustrations'; +import { useMemoizedLazyAsset } from '@hooks/useLazyAsset'; +import { loadIllustration } from '@components/Icon/IllustrationLoader'; import RadioListItem from '@components/SelectionList/RadioListItem'; import type {SelectorType} from '@components/SelectionScreen'; import SelectionScreen from '@components/SelectionScreen'; @@ -22,6 +23,7 @@ import ROUTES from '@src/ROUTES'; function NetSuiteApprovalAccountSelectPage({policy}: WithPolicyConnectionsProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); + const {asset: TeleScope} = useMemoizedLazyAsset(() => loadIllustration('Telescope')); const policyID = policy?.id; @@ -48,7 +50,7 @@ function NetSuiteApprovalAccountSelectPage({policy}: WithPolicyConnectionsProps) const listEmptyContent = useMemo( () => ( loadIllustration('Telescope')); const config = policy?.connections?.netsuite?.options.config; const netsuiteCollectionAccountOptions = useMemo( @@ -46,7 +48,7 @@ function NetSuiteCollectionAccountSelectPage({policy}: WithPolicyConnectionsProp const listEmptyContent = useMemo( () => ( loadIllustration('Telescope')); const config = policy?.connections?.netsuite?.options.config; const netsuiteReimbursableAccountOptions = useMemo( @@ -46,7 +48,7 @@ function NetSuiteReimbursementAccountSelectPage({policy}: WithPolicyConnectionsP const listEmptyContent = useMemo( () => ( loadIllustration('Telescope')); const route = useRoute>(); const params = route.params; @@ -68,7 +70,7 @@ function NetSuiteExportExpensesPayableAccountSelectPage({policy}: WithPolicyConn containerStyle={styles.pb10} /> ), - [translate, styles.pb10], + [translate, styles.pb10, TeleScope], ); return ( diff --git a/src/pages/workspace/accounting/netsuite/export/NetSuiteExportExpensesVendorSelectPage.tsx b/src/pages/workspace/accounting/netsuite/export/NetSuiteExportExpensesVendorSelectPage.tsx index a7440453da3f..cc9f9ae84f8d 100644 --- a/src/pages/workspace/accounting/netsuite/export/NetSuiteExportExpensesVendorSelectPage.tsx +++ b/src/pages/workspace/accounting/netsuite/export/NetSuiteExportExpensesVendorSelectPage.tsx @@ -1,7 +1,8 @@ import {useRoute} from '@react-navigation/native'; import React, {useCallback, useMemo} from 'react'; import BlockingView from '@components/BlockingViews/BlockingView'; -import {TeleScope} from '@components/Icon/Illustrations'; +import { useMemoizedLazyAsset } from '@hooks/useLazyAsset'; +import { loadIllustration } from '@components/Icon/IllustrationLoader'; import RadioListItem from '@components/SelectionList/RadioListItem'; import type {SelectorType} from '@components/SelectionScreen'; import SelectionScreen from '@components/SelectionScreen'; @@ -26,6 +27,7 @@ function NetSuiteExportExpensesVendorSelectPage({policy}: WithPolicyConnectionsP const {translate} = useLocalize(); const policyID = policy?.id; + const {asset: TeleScope} = useMemoizedLazyAsset(() => loadIllustration('Telescope')); const route = useRoute>(); const params = route.params; @@ -62,7 +64,7 @@ function NetSuiteExportExpensesVendorSelectPage({policy}: WithPolicyConnectionsP containerStyle={styles.pb10} /> ), - [translate, styles.pb10], + [translate, styles.pb10, TeleScope], ); return ( diff --git a/src/pages/workspace/accounting/netsuite/export/NetSuiteInvoiceItemSelectPage.tsx b/src/pages/workspace/accounting/netsuite/export/NetSuiteInvoiceItemSelectPage.tsx index cfa04f5ad81e..878b0d82b2d8 100644 --- a/src/pages/workspace/accounting/netsuite/export/NetSuiteInvoiceItemSelectPage.tsx +++ b/src/pages/workspace/accounting/netsuite/export/NetSuiteInvoiceItemSelectPage.tsx @@ -1,9 +1,10 @@ import React, {useCallback, useMemo} from 'react'; import BlockingView from '@components/BlockingViews/BlockingView'; -import * as Illustrations from '@components/Icon/Illustrations'; +import {loadIllustration} from '@components/Icon/IllustrationLoader'; import RadioListItem from '@components/SelectionList/RadioListItem'; import type {SelectorType} from '@components/SelectionScreen'; import SelectionScreen from '@components/SelectionScreen'; +import {useMemoizedLazyAsset} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import {updateNetSuiteInvoiceItem} from '@libs/actions/connections/NetSuiteCommands'; @@ -20,6 +21,7 @@ import ROUTES from '@src/ROUTES'; function NetSuiteInvoiceItemSelectPage({policy}: WithPolicyConnectionsProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); + const {asset: TeleScope} = useMemoizedLazyAsset(() => loadIllustration('Telescope')); const policyID = policy?.id; @@ -41,7 +43,7 @@ function NetSuiteInvoiceItemSelectPage({policy}: WithPolicyConnectionsProps) { const listEmptyContent = useMemo( () => ( loadIllustration('Telescope')); const policyID = policy?.id; @@ -47,7 +49,7 @@ function NetSuiteProvincialTaxPostingAccountSelectPage({policy}: WithPolicyConne const listEmptyContent = useMemo( () => ( loadIllustration('Telescope')); const policyID = policy?.id; const route = useRoute>(); @@ -60,7 +62,7 @@ function NetSuiteReceivableAccountSelectPage({policy}: WithPolicyConnectionsProp containerStyle={styles.pb10} /> ), - [translate, styles.pb10], + [translate, styles.pb10, TeleScope], ); return ( diff --git a/src/pages/workspace/accounting/netsuite/export/NetSuiteTaxPostingAccountSelectPage.tsx b/src/pages/workspace/accounting/netsuite/export/NetSuiteTaxPostingAccountSelectPage.tsx index cb56debc4a95..f8b61134bf5b 100644 --- a/src/pages/workspace/accounting/netsuite/export/NetSuiteTaxPostingAccountSelectPage.tsx +++ b/src/pages/workspace/accounting/netsuite/export/NetSuiteTaxPostingAccountSelectPage.tsx @@ -1,9 +1,10 @@ import React, {useCallback, useMemo} from 'react'; import BlockingView from '@components/BlockingViews/BlockingView'; -import * as Illustrations from '@components/Icon/Illustrations'; +import {loadIllustration} from '@components/Icon/IllustrationLoader'; import RadioListItem from '@components/SelectionList/RadioListItem'; import type {SelectorType} from '@components/SelectionScreen'; import SelectionScreen from '@components/SelectionScreen'; +import {useMemoizedLazyAsset} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import usePermissions from '@hooks/usePermissions'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -21,6 +22,7 @@ import ROUTES from '@src/ROUTES'; function NetSuiteTaxPostingAccountSelectPage({policy}: WithPolicyConnectionsProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); + const {asset: TeleScope} = useMemoizedLazyAsset(() => loadIllustration('Telescope')); const {isBetaEnabled} = usePermissions(); const policyID = policy?.id; @@ -49,7 +51,7 @@ function NetSuiteTaxPostingAccountSelectPage({policy}: WithPolicyConnectionsProp const listEmptyContent = useMemo( () => ( >(); const backTo = route.params?.backTo; - + const {asset: TeleScope} = useMemoizedLazyAsset(() => loadIllustration('Telescope')); const data: CardListItem[] = useMemo(() => { const accounts = getQBDReimbursableAccounts(policy?.connections?.quickbooksDesktop, nonReimbursable); return accounts.map((card) => ({ @@ -68,7 +69,7 @@ function QuickbooksDesktopCompanyCardExpenseAccountSelectPage({policy}: WithPoli const listEmptyContent = useMemo( () => ( ), - [translate, styles.pb10], + [translate, styles.pb10, TeleScope], ); return ( loadIllustration('Telescope')); const {vendors} = policy?.connections?.quickbooksDesktop?.data ?? {}; const qbdConfig = policy?.connections?.quickbooksDesktop?.config; const nonReimbursableBillDefaultVendor = qbdConfig?.export?.nonReimbursableBillDefaultVendor; @@ -55,7 +57,7 @@ function QuickbooksDesktopNonReimbursableDefaultVendorSelectPage({policy}: WithP const listEmptyContent = useMemo( () => ( loadIllustration('Telescope')); const qbdConfig = policy?.connections?.quickbooksDesktop?.config; const route = useRoute>(); const backTo = route.params?.backTo; @@ -89,7 +91,7 @@ function QuickbooksDesktopOutOfPocketExpenseAccountSelectPage({policy}: WithPoli const listEmptyContent = useMemo( () => ( loadIllustration('Telescope')); const policyID = policy?.id ?? '-1'; const {bankAccounts, creditCards} = policy?.connections?.quickbooksOnline?.data ?? {}; @@ -64,7 +66,7 @@ function QuickbooksAccountSelectPage({policy}: WithPolicyConnectionsProps) { const listEmptyContent = useMemo( () => ( loadIllustration('Telescope')); const policyID = policy?.id ?? '-1'; const {bankAccounts, otherCurrentAssetAccounts} = policy?.connections?.quickbooksOnline?.data ?? {}; @@ -65,7 +67,7 @@ function QuickbooksInvoiceAccountSelectPage({policy}: WithPolicyConnectionsProps const listEmptyContent = useMemo( () => ( loadIllustration('Telescope')); const policyID = policy?.id; const {creditCards, accountPayable, bankAccounts} = policy?.connections?.quickbooksOnline?.data ?? {}; const qboConfig = policy?.connections?.quickbooksOnline?.config; @@ -86,7 +88,7 @@ function QuickbooksCompanyCardExpenseAccountSelectPage({policy}: WithPolicyConne containerStyle={styles.pb10} /> ), - [translate, styles.pb10], + [translate, styles.pb10, TeleScope], ); return ( loadIllustration('Telescope')); const {accountsReceivable} = policy?.connections?.quickbooksOnline?.data ?? {}; const qboConfig = policy?.connections?.quickbooksOnline?.config; const route = useRoute>(); @@ -64,7 +66,7 @@ function QuickbooksExportInvoiceAccountSelectPage({policy}: WithPolicyConnection const listEmptyContent = useMemo( () => ( loadIllustration('Telescope')); const {vendors} = policy?.connections?.quickbooksOnline?.data ?? {}; const qboConfig = policy?.connections?.quickbooksOnline?.config; @@ -52,7 +54,7 @@ function QuickbooksNonReimbursableDefaultVendorSelectPage({policy}: WithPolicyCo const listEmptyContent = useMemo( () => ( >(); const backTo = route.params?.backTo; + const {asset: TeleScope} = useMemoizedLazyAsset(() => loadIllustration('Telescope')); const [title, description] = useMemo(() => { let titleText: TranslationPaths | undefined; @@ -109,7 +111,7 @@ function QuickbooksOutOfPocketExpenseAccountSelectPage({policy}: WithPolicyConne containerStyle={styles.pb10} /> ), - [translate, styles.pb10], + [translate, styles.pb10, TeleScope], ); return ( diff --git a/src/pages/workspace/accounting/xero/XeroOrganizationConfigurationPage.tsx b/src/pages/workspace/accounting/xero/XeroOrganizationConfigurationPage.tsx index adcf515719a8..8e565b2dbd0c 100644 --- a/src/pages/workspace/accounting/xero/XeroOrganizationConfigurationPage.tsx +++ b/src/pages/workspace/accounting/xero/XeroOrganizationConfigurationPage.tsx @@ -1,7 +1,8 @@ import React, {useMemo} from 'react'; import {View} from 'react-native'; import BlockingView from '@components/BlockingViews/BlockingView'; -import * as Illustrations from '@components/Icon/Illustrations'; +import { useMemoizedLazyAsset } from '@hooks/useLazyAsset'; +import { loadIllustration } from '@components/Icon/IllustrationLoader'; import RadioListItem from '@components/SelectionList/RadioListItem'; import type {ListItem} from '@components/SelectionList/types'; import SelectionScreen from '@components/SelectionScreen'; @@ -36,6 +37,7 @@ function XeroOrganizationConfigurationPage({ const currentXeroOrganization = findCurrentXeroOrganization(tenants, xeroConfig?.tenantID); const policyID = policy?.id ?? '-1'; + const {asset: TeleScope} = useMemoizedLazyAsset(() => loadIllustration('Telescope')); const sections = policy?.connections?.xero?.data?.tenants.map((tenant) => ({ @@ -66,7 +68,7 @@ function XeroOrganizationConfigurationPage({ const listEmptyContent = useMemo( () => ( loadIllustration('Telescope')); const {config} = policy?.connections?.xero ?? {}; const {reimbursementAccountID, syncReimbursedReports} = policy?.connections?.xero?.config.sync ?? {}; const xeroSelectorOptions = useMemo(() => getXeroBankAccounts(policy ?? undefined, reimbursementAccountID), [reimbursementAccountID, policy]); @@ -51,7 +52,7 @@ function XeroBillPaymentAccountSelectorPage({policy}: WithPolicyConnectionsProps const listEmptyContent = useMemo( () => ( ), - [translate, styles.pb10], + [translate, styles.pb10, TeleScope], ); return ( diff --git a/src/pages/workspace/accounting/xero/advanced/XeroInvoiceAccountSelectorPage.tsx b/src/pages/workspace/accounting/xero/advanced/XeroInvoiceAccountSelectorPage.tsx index d3bf2c894039..0aedb993a6b2 100644 --- a/src/pages/workspace/accounting/xero/advanced/XeroInvoiceAccountSelectorPage.tsx +++ b/src/pages/workspace/accounting/xero/advanced/XeroInvoiceAccountSelectorPage.tsx @@ -1,7 +1,8 @@ import React, {useCallback, useMemo} from 'react'; import {View} from 'react-native'; import BlockingView from '@components/BlockingViews/BlockingView'; -import * as Illustrations from '@components/Icon/Illustrations'; +import { useMemoizedLazyAsset } from '@hooks/useLazyAsset'; +import { loadIllustration } from '@components/Icon/IllustrationLoader'; import RadioListItem from '@components/SelectionList/RadioListItem'; import type {SelectorType} from '@components/SelectionScreen'; import SelectionScreen from '@components/SelectionScreen'; @@ -24,7 +25,7 @@ function XeroInvoiceAccountSelectorPage({policy}: WithPolicyConnectionsProps) { const {translate} = useLocalize(); const policyID = policy?.id ?? '-1'; - + const {asset: TeleScope} = useMemoizedLazyAsset(() => loadIllustration('Telescope')); const {config} = policy?.connections?.xero ?? {}; const {invoiceCollectionsAccountID, syncReimbursedReports} = policy?.connections?.xero?.config.sync ?? {}; const xeroSelectorOptions = useMemo(() => getXeroBankAccounts(policy ?? undefined, invoiceCollectionsAccountID), [invoiceCollectionsAccountID, policy]); @@ -51,7 +52,7 @@ function XeroInvoiceAccountSelectorPage({policy}: WithPolicyConnectionsProps) { const listEmptyContent = useMemo( () => ( ), - [translate, styles.pb10], + [translate, styles.pb10, TeleScope], ); return ( diff --git a/src/pages/workspace/accounting/xero/export/XeroBankAccountSelectPage.tsx b/src/pages/workspace/accounting/xero/export/XeroBankAccountSelectPage.tsx index 798ed02473d2..071ac04372d6 100644 --- a/src/pages/workspace/accounting/xero/export/XeroBankAccountSelectPage.tsx +++ b/src/pages/workspace/accounting/xero/export/XeroBankAccountSelectPage.tsx @@ -2,8 +2,10 @@ import {useRoute} from '@react-navigation/native'; import React, {useCallback, useMemo} from 'react'; import {View} from 'react-native'; import BlockingView from '@components/BlockingViews/BlockingView'; -import {TeleScope} from '@components/Icon/Illustrations'; import RadioListItem from '@components/SelectionList/RadioListItem'; +import { useMemoizedLazyAsset } from '@hooks/useLazyAsset'; +import { loadIllustration } from '@components/Icon/IllustrationLoader'; + import type {SelectorType} from '@components/SelectionScreen'; import SelectionScreen from '@components/SelectionScreen'; import Text from '@components/Text'; @@ -26,6 +28,7 @@ import type SCREENS from '@src/SCREENS'; function XeroBankAccountSelectPage({policy}: WithPolicyConnectionsProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); + const {asset: TeleScope} = useMemoizedLazyAsset(() => loadIllustration('Telescope')); const policyID = policy?.id; const {config} = policy?.connections?.xero ?? {}; @@ -74,7 +77,7 @@ function XeroBankAccountSelectPage({policy}: WithPolicyConnectionsProps) { containerStyle={styles.pb10} /> ), - [translate, styles.pb10], + [translate, styles.pb10, TeleScope], ); return ( diff --git a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx index f4ecc1c4fe45..133c9d052d7e 100644 --- a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx +++ b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx @@ -7,8 +7,8 @@ import ConfirmModal from '@components/ConfirmModal'; import DecisionModal from '@components/DecisionModal'; import EmptyStateComponent from '@components/EmptyStateComponent'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import {loadIllustration} from '@components/Icon/IllustrationLoader'; import * as Expensicons from '@components/Icon/Expensicons'; -import * as Illustrations from '@components/Icon/Illustrations'; import ImportedFromAccountingSoftware from '@components/ImportedFromAccountingSoftware'; import LottieAnimations from '@components/LottieAnimations'; import RenderHTML from '@components/RenderHTML'; @@ -25,6 +25,7 @@ import Text from '@components/Text'; import useAutoTurnSelectionModeOffWhenHasNoActiveOption from '@hooks/useAutoTurnSelectionModeOffWhenHasNoActiveOption'; import useCleanupSelectedOptions from '@hooks/useCleanupSelectedOptions'; import useEnvironment from '@hooks/useEnvironment'; +import {useMemoizedLazyAsset} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useMobileSelectionMode from '@hooks/useMobileSelectionMode'; import useNetwork from '@hooks/useNetwork'; @@ -91,6 +92,7 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { const [selectedCategories, setSelectedCategories] = useState([]); const canSelectMultiple = isSmallScreenWidth ? isMobileSelectionModeEnabled : true; + const {asset: FolderOpenIcon} = useMemoizedLazyAsset(() => loadIllustration('FolderOpen')); const fetchCategories = useCallback(() => { openPolicyCategoriesPage(policyId); @@ -491,7 +493,7 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { { if (isMobileSelectionModeEnabled) { diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardAccountSelectCardPage.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardAccountSelectCardPage.tsx index a4595d5f8a4c..68329ef64d97 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardAccountSelectCardPage.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardAccountSelectCardPage.tsx @@ -1,7 +1,8 @@ import React, {useCallback, useMemo, useState} from 'react'; import {View} from 'react-native'; import BlockingView from '@components/BlockingViews/BlockingView'; -import {TeleScope} from '@components/Icon/Illustrations'; +import { useMemoizedLazyAsset } from '@hooks/useLazyAsset'; +import { loadIllustration } from '@components/Icon/IllustrationLoader'; import RadioListItem from '@components/SelectionList/RadioListItem'; import SelectionScreen from '@components/SelectionScreen'; import type {SelectorType} from '@components/SelectionScreen'; @@ -46,6 +47,7 @@ function WorkspaceCompanyCardAccountSelectCardPage({route}: WorkspaceCompanyCard const shouldShowTextInput = (exportMenuItem?.data?.length ?? 0) >= CONST.STANDARD_LIST_ITEM_LIMIT; const defaultCard = translate('workspace.moreFeatures.companyCards.defaultCard'); const isXeroConnection = connectedIntegration === CONST.POLICY.CONNECTIONS.NAME.XERO; + const {asset: TeleScope} = useMemoizedLazyAsset(() => loadIllustration('Telescope')); const [cardFeeds] = useCardFeeds(policyID); const companyFeeds = getCompanyFeeds(cardFeeds); @@ -66,7 +68,7 @@ function WorkspaceCompanyCardAccountSelectCardPage({route}: WorkspaceCompanyCard containerStyle={styles.pb10} /> ), - [translate, currentConnectionName, styles], + [translate, currentConnectionName, styles, TeleScope], ); const updateExportAccount = useCallback( diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardDetailsPage.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardDetailsPage.tsx index 6c8423ea63ac..0537a9e5e146 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardDetailsPage.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardDetailsPage.tsx @@ -5,6 +5,8 @@ import ConfirmModal from '@components/ConfirmModal'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import {FallbackAvatar} from '@components/Icon/Expensicons'; import * as Expensicons from '@components/Icon/Expensicons'; +import {useMemoizedLazyAsset} from '@hooks/useLazyAsset'; +import {loadIllustration} from '@components/Icon/IllustrationLoader'; import ImageSVG from '@components/ImageSVG'; import MenuItem from '@components/MenuItem'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; @@ -55,6 +57,8 @@ function WorkspaceCompanyCardDetailsPage({route}: WorkspaceCompanyCardDetailsPag const styles = useThemeStyles(); const theme = useTheme(); const illustrations = useThemeIllustrations(); + const {asset: MoneySearch} = useMemoizedLazyAsset(() => loadIllustration('MoneySearch')); + const {isOffline} = useNetwork(); const accountingIntegrations = Object.values(CONST.POLICY.CONNECTIONS.NAME); const connectedIntegration = getConnectedIntegration(policy, accountingIntegrations) ?? connectionSyncProgress?.connectionName; @@ -63,6 +67,7 @@ function WorkspaceCompanyCardDetailsPage({route}: WorkspaceCompanyCardDetailsPag const [allBankCards, allBankCardsMetadata] = useCardsList(policyID, bank as CompanyCardFeed); const card = allBankCards?.[cardID]; + const cardBank = card?.bank ?? ''; const cardholder = personalDetails?.[card?.accountID ?? CONST.DEFAULT_NUMBER_ID]; const displayName = getDisplayNameOrDefault(cardholder); @@ -194,7 +199,7 @@ function WorkspaceCompanyCardDetailsPage({route}: WorkspaceCompanyCardDetailsPag interactive={false} /> { diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardPageEmptyState.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardPageEmptyState.tsx index 7143ebf8681f..d71f52714f69 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardPageEmptyState.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardPageEmptyState.tsx @@ -1,10 +1,11 @@ -import React, {useCallback, useContext} from 'react'; +import React, {useCallback, useContext, useMemo} from 'react'; import {View} from 'react-native'; import {DelegateNoAccessContext} from '@components/DelegateNoAccessModalProvider'; import FeatureList from '@components/FeatureList'; import type {FeatureListItem} from '@components/FeatureList'; -import {CompanyCardsEmptyState, CreditCardsNew, HandCard, MagnifyingGlassMoney} from '@components/Icon/Illustrations'; +import {loadIllustration} from '@components/Icon/IllustrationLoader'; import Text from '@components/Text'; +import {useMemoizedLazyAsset} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; @@ -20,21 +21,6 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import WorkspaceCompanyCardExpensifyCardPromotionBanner from './WorkspaceCompanyCardExpensifyCardPromotionBanner'; -const companyCardFeatures: FeatureListItem[] = [ - { - icon: CreditCardsNew, - translationKey: 'workspace.moreFeatures.companyCards.feed.features.support', - }, - { - icon: HandCard, - translationKey: 'workspace.moreFeatures.companyCards.feed.features.assignCards', - }, - { - icon: MagnifyingGlassMoney, - translationKey: 'workspace.moreFeatures.companyCards.feed.features.automaticImport', - }, -]; - type WorkspaceCompanyCardPageEmptyStateProps = { shouldShowGBDisclaimer?: boolean; } & WithPolicyAndFullscreenLoadingProps; @@ -48,6 +34,36 @@ function WorkspaceCompanyCardPageEmptyState({policy, shouldShowGBDisclaimer}: Wo const shouldShowExpensifyCardPromotionBanner = !hasIssuedExpensifyCard(policy?.workspaceAccountID ?? CONST.DEFAULT_NUMBER_ID, allWorkspaceCards); const workspaceAccountID = policy?.workspaceAccountID ?? CONST.DEFAULT_NUMBER_ID; + const {asset: CreditCardsIcon} = useMemoizedLazyAsset(() => loadIllustration('CreditCardsNew')); + const {asset: HandCardIcon} = useMemoizedLazyAsset(() => loadIllustration('HandCard')); + const {asset: MagnifyingGlassIcon} = useMemoizedLazyAsset(() => loadIllustration('MagnifyingGlassMoney')); + const {asset: CompanyCardsEmptyStateIcon} = useMemoizedLazyAsset(() => loadIllustration('CompanyCardsEmptyState')); + + const companyCardFeatures = useMemo(() => { + const features = [ + { + icon: CreditCardsIcon, + translationKey: 'workspace.moreFeatures.companyCards.feed.features.support' as const, + }, + + { + icon: HandCardIcon, + translationKey: 'workspace.moreFeatures.companyCards.feed.features.assignCards' as const, + }, + + { + icon: MagnifyingGlassIcon, + translationKey: 'workspace.moreFeatures.companyCards.feed.features.automaticImport' as const, + }, + ]; + return features + .filter((feature) => feature.icon !== null) + .map((feature) => ({ + icon: feature.icon, + translationKey: feature.translationKey, + })); + }, [CreditCardsIcon, HandCardIcon, MagnifyingGlassIcon]); + const handleCtaPress = useCallback(() => { if (!policy?.id) { return; @@ -64,14 +80,14 @@ function WorkspaceCompanyCardPageEmptyState({policy, shouldShowGBDisclaimer}: Wo {shouldShowExpensifyCardPromotionBanner && } loadIllustration('CompanyCardsEmptyState')); return ( loadIllustration('CompanyCard')); const policyID = route.params.policyID; const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {canBeMissing: false}); const workspaceAccountID = policy?.workspaceAccountID ?? CONST.DEFAULT_NUMBER_ID; @@ -173,7 +175,7 @@ function WorkspaceCompanyCardsPage({route}: WorkspaceCompanyCardsPageProps) { {!isLoading && ( loadIllustration('MoneySearch')); // We need to use isSmallScreenWidth instead of shouldUseNarrowLayout to use the correct modal type for the decision modal // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth const {isSmallScreenWidth} = useResponsiveLayout(); @@ -194,7 +197,7 @@ function WorkspaceExpensifyCardDetailsPage({route}: WorkspaceExpensifyCardDetail /> { diff --git a/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardListPage.tsx b/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardListPage.tsx index b0c0cda4a6b2..3939c43421e8 100644 --- a/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardListPage.tsx +++ b/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardListPage.tsx @@ -8,8 +8,8 @@ import ButtonWithDropdownMenu from '@components/ButtonWithDropdownMenu'; import {DelegateNoAccessContext} from '@components/DelegateNoAccessModalProvider'; import FeedSelector from '@components/FeedSelector'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import {loadIllustration} from '@components/Icon/IllustrationLoader'; import {Gear, Plus} from '@components/Icon/Expensicons'; -import {HandCard} from '@components/Icon/Illustrations'; import {LockedAccountContext} from '@components/LockedAccountModalProvider'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import {PressableWithFeedback} from '@components/Pressable'; @@ -22,6 +22,7 @@ import useEmptyViewHeaderHeight from '@hooks/useEmptyViewHeaderHeight'; import useExpensifyCardFeeds from '@hooks/useExpensifyCardFeeds'; import useExpensifyCardUkEuSupported from '@hooks/useExpensifyCardUkEuSupported'; import useHandleBackButton from '@hooks/useHandleBackButton'; +import {useMemoizedLazyAsset} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import usePolicy from '@hooks/usePolicy'; @@ -62,6 +63,7 @@ function WorkspaceExpensifyCardListPage({route, cardsList, fundID}: WorkspaceExp const {shouldUseNarrowLayout, isMediumScreenWidth} = useResponsiveLayout(); const {translate, localeCompare} = useLocalize(); const styles = useThemeStyles(); + const {asset: HandCard} = useMemoizedLazyAsset(() => loadIllustration('HandCard')); const policyID = route.params.policyID; const policy = usePolicy(policyID); diff --git a/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardPageEmptyState.tsx b/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardPageEmptyState.tsx index 42c0903994c8..95a543dc6f40 100644 --- a/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardPageEmptyState.tsx +++ b/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardPageEmptyState.tsx @@ -1,14 +1,15 @@ -import React, {useCallback, useContext} from 'react'; +import React, {useCallback, useContext, useMemo} from 'react'; import {View} from 'react-native'; import ConfirmModal from '@components/ConfirmModal'; import {DelegateNoAccessContext} from '@components/DelegateNoAccessModalProvider'; import FeatureList from '@components/FeatureList'; import type {FeatureListItem} from '@components/FeatureList'; -import * as Illustrations from '@components/Icon/Illustrations'; +import {loadIllustration} from '@components/Icon/IllustrationLoader'; import {LockedAccountContext} from '@components/LockedAccountModalProvider'; import Text from '@components/Text'; import useDismissModalForUSD from '@hooks/useDismissModalForUSD'; import useExpensifyCardUkEuSupported from '@hooks/useExpensifyCardUkEuSupported'; +import {useMemoizedLazyAsset} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; @@ -31,26 +32,16 @@ import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -const expensifyCardFeatures: FeatureListItem[] = [ - { - icon: Illustrations.MoneyReceipts, - translationKey: 'workspace.moreFeatures.expensifyCard.feed.features.cashBack', - }, - { - icon: Illustrations.CreditCardsNew, - translationKey: 'workspace.moreFeatures.expensifyCard.feed.features.unlimited', - }, - { - icon: Illustrations.MoneyWings, - translationKey: 'workspace.moreFeatures.expensifyCard.feed.features.spend', - }, -]; - type WorkspaceExpensifyCardPageEmptyStateProps = { route: PlatformStackScreenProps['route']; } & WithPolicyAndFullscreenLoadingProps; function WorkspaceExpensifyCardPageEmptyState({route, policy}: WorkspaceExpensifyCardPageEmptyStateProps) { + const {asset: MoneyReceiptsIcon} = useMemoizedLazyAsset(() => loadIllustration('MoneyReceipts')); + const {asset: CreditCardsNewIcon} = useMemoizedLazyAsset(() => loadIllustration('CreditCardsNew')); + const {asset: MoneyWingsIcon} = useMemoizedLazyAsset(() => loadIllustration('MoneyWings')); + const {asset: HandCardIcon} = useMemoizedLazyAsset(() => loadIllustration('HandCard')); + const {asset: ExpensifyCardIllustrationIcon} = useMemoizedLazyAsset(() => loadIllustration('ExpensifyCardIllustration')); const {translate} = useLocalize(); const styles = useThemeStyles(); const theme = useTheme(); @@ -76,6 +67,29 @@ function WorkspaceExpensifyCardPageEmptyState({route, policy}: WorkspaceExpensif } }, [eligibleBankAccounts.length, isSetupUnfinished, policy?.id]); + const expensifyCardFeatures: FeatureListItem[] = useMemo(() => { + const features = [ + { + icon: MoneyReceiptsIcon, + translationKey: 'workspace.moreFeatures.expensifyCard.feed.features.cashBack' as const, + }, + { + icon: CreditCardsNewIcon, + translationKey: 'workspace.moreFeatures.expensifyCard.feed.features.unlimited' as const, + }, + { + icon: MoneyWingsIcon, + translationKey: 'workspace.moreFeatures.expensifyCard.feed.features.spend' as const, + }, + ]; + return features + .filter((feature) => feature.icon !== null) + .map((feature) => ({ + icon: feature.icon, + translationKey: feature.translationKey, + })); + }, [CreditCardsNewIcon, MoneyReceiptsIcon, MoneyWingsIcon]); + const confirmCurrencyChangeAndHideModal = useCallback(() => { if (!policy) { return; @@ -88,7 +102,7 @@ function WorkspaceExpensifyCardPageEmptyState({route, policy}: WorkspaceExpensif return ( diff --git a/src/pages/workspace/expensifyCard/issueNew/CardTypeStep.tsx b/src/pages/workspace/expensifyCard/issueNew/CardTypeStep.tsx index 18060016de44..0af3c6601de2 100644 --- a/src/pages/workspace/expensifyCard/issueNew/CardTypeStep.tsx +++ b/src/pages/workspace/expensifyCard/issueNew/CardTypeStep.tsx @@ -2,6 +2,8 @@ import React from 'react'; import {View} from 'react-native'; import type {ValueOf} from 'type-fest'; import * as Illustrations from '@components/Icon/Illustrations'; +import {loadIllustration} from '@components/Icon/IllustrationLoader'; +import {useMemoizedLazyAsset} from '@hooks/useLazyAsset'; import InteractiveStepWrapper from '@components/InteractiveStepWrapper'; import MenuItem from '@components/MenuItem'; import Text from '@components/Text'; @@ -28,6 +30,7 @@ type CardTypeStepProps = { function CardTypeStep({policyID, stepNames, startStepIndex}: CardTypeStepProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); + const {asset: HandCardIcon} = useMemoizedLazyAsset(() => loadIllustration('HandCard')); const [issueNewCard] = useOnyx(`${ONYXKEYS.COLLECTION.ISSUE_NEW_EXPENSIFY_CARD}${policyID}`, {canBeMissing: true}); const isEditing = issueNewCard?.isEditing; @@ -71,7 +74,7 @@ function CardTypeStep({policyID, stepNames, startStepIndex}: CardTypeStepProps) {translate('workspace.card.issueNewCard.chooseCardType')} loadIllustration('InvoiceBlue')); return ( {(_hasVBA?: boolean, policyID?: string) => ( diff --git a/src/pages/workspace/perDiem/WorkspacePerDiemPage.tsx b/src/pages/workspace/perDiem/WorkspacePerDiemPage.tsx index 242a2cc79c05..a2d1db73113e 100644 --- a/src/pages/workspace/perDiem/WorkspacePerDiemPage.tsx +++ b/src/pages/workspace/perDiem/WorkspacePerDiemPage.tsx @@ -7,8 +7,8 @@ import ConfirmModal from '@components/ConfirmModal'; import DecisionModal from '@components/DecisionModal'; import EmptyStateComponent from '@components/EmptyStateComponent'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import {loadIllustration} from '@components/Icon/IllustrationLoader'; import * as Expensicons from '@components/Icon/Expensicons'; -import * as Illustrations from '@components/Icon/Illustrations'; import LottieAnimations from '@components/LottieAnimations'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; @@ -20,6 +20,7 @@ import TableListItemSkeleton from '@components/Skeletons/TableRowSkeleton'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; import useCleanupSelectedOptions from '@hooks/useCleanupSelectedOptions'; +import {useMemoizedLazyAsset} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useMobileSelectionMode from '@hooks/useMobileSelectionMode'; import useNetwork from '@hooks/useNetwork'; @@ -128,6 +129,7 @@ function WorkspacePerDiemPage({route}: WorkspacePerDiemPageProps) { const policy = usePolicy(policyID); const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, {canBeMissing: false}); const isMobileSelectionModeEnabled = useMobileSelectionMode(); + const {asset: PerDiemIcon} = useMemoizedLazyAsset(() => loadIllustration('PerDiem')); const [customUnit, allRatesArray, allSubRates] = useMemo(() => { const customUnits = getPerDiemCustomUnit(policy); @@ -410,7 +412,7 @@ function WorkspacePerDiemPage({route}: WorkspacePerDiemPageProps) { { if (isMobileSelectionModeEnabled) { diff --git a/src/pages/workspace/reports/WorkspaceReportsPage.tsx b/src/pages/workspace/reports/WorkspaceReportsPage.tsx index b0e1ed454605..e7d83155587e 100644 --- a/src/pages/workspace/reports/WorkspaceReportsPage.tsx +++ b/src/pages/workspace/reports/WorkspaceReportsPage.tsx @@ -5,9 +5,8 @@ import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {ActivityIndicator, View} from 'react-native'; import ConfirmModal from '@components/ConfirmModal'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import {loadIllustration} from '@components/Icon/IllustrationLoader'; import {Plus} from '@components/Icon/Expensicons'; -import {ReportReceipt} from '@components/Icon/Illustrations'; -import ImportedFromAccountingSoftware from '@components/ImportedFromAccountingSoftware'; import MenuItem from '@components/MenuItem'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; @@ -17,6 +16,9 @@ import ScrollView from '@components/ScrollView'; import Section from '@components/Section'; import type {ListItem} from '@components/SelectionList/types'; import Text from '@components/Text'; +import TextLink from '@components/TextLink'; +import useEnvironment from '@hooks/useEnvironment'; +import {useMemoizedLazyAsset} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; @@ -67,6 +69,7 @@ function WorkspaceReportFieldsPage({ const [isReportFieldsWarningModalOpen, setIsReportFieldsWarningModalOpen] = useState(false); const policy = usePolicy(policyID); const [connectionSyncProgress] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS}${policyID}`, {canBeMissing: true}); + const {environmentURL} = useEnvironment(); const isSyncInProgress = isConnectionInProgress(connectionSyncProgress, policy); const hasSyncError = shouldShowSyncError(policy, isSyncInProgress); const connectedIntegration = getConnectedIntegration(policy) ?? connectionSyncProgress?.connectionName; @@ -82,6 +85,8 @@ function WorkspaceReportFieldsPage({ }, [policy]); const [isOrganizeWarningModalOpen, setIsOrganizeWarningModalOpen] = useState(false); + const {asset: ReportReceipt} = useMemoizedLazyAsset(() => loadIllustration('ReportReceipt')); + const onDisabledOrganizeSwitchPress = useCallback(() => { if (!hasAccountingConnections) { return; @@ -123,14 +128,16 @@ function WorkspaceReportFieldsPage({ ); const getHeaderText = () => - !hasSyncError && isConnectionVerified && currentConnectionName ? ( + !hasSyncError && isConnectionVerified ? ( - + {`${translate('workspace.reportFields.importedFromAccountingSoftware')} `} + + {`${currentConnectionName} ${translate('workspace.accounting.settings')}`} + + . ) : ( {translate('workspace.reportFields.subtitle')} diff --git a/src/pages/workspace/rules/PolicyRulesPage.tsx b/src/pages/workspace/rules/PolicyRulesPage.tsx index ddda634e8ddb..227128bf199b 100644 --- a/src/pages/workspace/rules/PolicyRulesPage.tsx +++ b/src/pages/workspace/rules/PolicyRulesPage.tsx @@ -1,5 +1,7 @@ import React from 'react'; import {View} from 'react-native'; +import {loadIllustration} from '@components/Icon/IllustrationLoader'; +import {useMemoizedLazyAsset} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -7,7 +9,6 @@ import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavig import type {WorkspaceSplitNavigatorParamList} from '@libs/Navigation/types'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; -import * as Illustrations from '@src/components/Icon/Illustrations'; import CONST from '@src/CONST'; import type SCREENS from '@src/SCREENS'; import ExpenseReportRulesSection from './ExpenseReportRulesSection'; @@ -20,6 +21,7 @@ function PolicyRulesPage({route}: PolicyRulesPageProps) { const {policyID} = route.params; const styles = useThemeStyles(); const {shouldUseNarrowLayout} = useResponsiveLayout(); + const {asset: RulesIcon} = useMemoizedLazyAsset(() => loadIllustration('Rules')); return ( loadIllustration('Tag')); const tagsList = useMemo(() => { if (isMultiLevelTags) { @@ -667,7 +669,7 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { offlineIndicatorStyle={styles.mtAuto} > !policy?.taxRates?.taxes[taxID]?.isDisabled).length; const disabledRatesCount = selectedTaxesIDs.length - enabledRatesCount; + const {asset: CoinsIcon} = useMemoizedLazyAsset(() => loadIllustration('Coins')); const fetchTaxes = useCallback(() => { openPolicyTaxesPage(policyID); @@ -395,7 +397,7 @@ function WorkspaceTaxesPage({ shouldShowOfflineIndicatorInWideScreen > loadIllustration('Workflows')); // We need to use isSmallScreenWidth instead of shouldUseNarrowLayout to apply a correct padding style // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth const {shouldUseNarrowLayout, isSmallScreenWidth} = useResponsiveLayout(); @@ -378,7 +379,7 @@ function WorkspaceWorkflowsPage({policy, route}: WorkspaceWorkflowsPageProps) { >