diff --git a/assets/images/expensifyCard/cardIllustration.svg b/assets/images/expensifyCard/cardIllustration.svg new file mode 100644 index 000000000000..f8162bbd913f --- /dev/null +++ b/assets/images/expensifyCard/cardIllustration.svg @@ -0,0 +1,487 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/Icon/Illustrations.ts b/src/components/Icon/Illustrations.ts index e699badc43ec..bd0824372799 100644 --- a/src/components/Icon/Illustrations.ts +++ b/src/components/Icon/Illustrations.ts @@ -1,3 +1,4 @@ +import ExpensifyCardIllustration from '@assets/images/expensifyCard/cardIllustration.svg'; import Abracadabra from '@assets/images/product-illustrations/abracadabra.svg'; import BankArrowPink from '@assets/images/product-illustrations/bank-arrow--pink.svg'; import BankMouseGreen from '@assets/images/product-illustrations/bank-mouse--green.svg'; @@ -176,6 +177,7 @@ export { Binoculars, CompanyCard, ReceiptUpload, + ExpensifyCardIllustration, SplitBill, PiggyBank, Accounting, diff --git a/src/languages/en.ts b/src/languages/en.ts index 936941003073..bf6b490a1c66 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2390,6 +2390,16 @@ export default { disableCardTitle: 'Disable Expensify Card', disableCardPrompt: 'You can’t disable the Expensify Card because it’s already in use. Reach out to Concierge for next steps.', disableCardButton: 'Chat with Concierge', + feed: { + title: 'Get the Expensify Card', + subTitle: 'Streamline your business with the Expensify Card', + features: { + cashBack: 'Up to 2% cash back on every US purchase', + unlimited: 'Issue unlimited virtual cards', + spend: 'Spend controls and custom limits', + }, + ctaTitle: 'Issue new card', + }, }, workflows: { title: 'Workflows', diff --git a/src/languages/es.ts b/src/languages/es.ts index 59aad3275c41..f3e6d88a470b 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2420,6 +2420,16 @@ export default { disableCardTitle: 'Deshabilitar la Tarjeta Expensify', disableCardPrompt: 'No puedes deshabilitar la Tarjeta Expensify porque ya está en uso. Por favor, contacta con Concierge para conocer los pasos a seguir.', disableCardButton: 'Chatear con Concierge', + feed: { + title: 'Consigue la Tarjeta Expensify', + subTitle: 'Optimiza tu negocio con la Tarjeta Expensify', + features: { + cashBack: 'Hasta un 2% de devolución en cada compra en Estadios Unidos', + unlimited: 'Emitir un número ilimitado de tarjetas virtuales', + spend: 'Controles de gastos y límites personalizados', + }, + ctaTitle: 'Emitir nueva tarjeta', + }, }, distanceRates: { title: 'Tasas de distancia', diff --git a/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx index 16e8404f5fe9..748d92b49a1c 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx @@ -19,7 +19,6 @@ type Screens = Partial React.Co const CENTRAL_PANE_WORKSPACE_SCREENS = { [SCREENS.WORKSPACE.PROFILE]: () => require('../../../../pages/workspace/WorkspaceProfilePage').default, [SCREENS.WORKSPACE.CARD]: () => require('../../../../pages/workspace/card/WorkspaceCardPage').default, - [SCREENS.WORKSPACE.EXPENSIFY_CARD]: () => require('../../../../pages/workspace/expensifyCard/WorkspaceExpensifyCardPage').default, [SCREENS.WORKSPACE.WORKFLOWS]: () => require('../../../../pages/workspace/workflows/WorkspaceWorkflowsPage').default, [SCREENS.WORKSPACE.REIMBURSE]: () => require('../../../../pages/workspace/reimburse/WorkspaceReimbursePage').default, [SCREENS.WORKSPACE.BILLS]: () => require('../../../../pages/workspace/bills/WorkspaceBillsPage').default, @@ -32,6 +31,7 @@ const CENTRAL_PANE_WORKSPACE_SCREENS = { [SCREENS.WORKSPACE.TAGS]: () => require('../../../../pages/workspace/tags/WorkspaceTagsPage').default, [SCREENS.WORKSPACE.TAXES]: () => require('../../../../pages/workspace/taxes/WorkspaceTaxesPage').default, [SCREENS.WORKSPACE.REPORT_FIELDS]: () => require('../../../../pages/workspace/reportFields/WorkspaceReportFieldsPage').default, + [SCREENS.WORKSPACE.EXPENSIFY_CARD]: () => require('../../../../pages/workspace/expensifyCard/WorkspaceExpensifyCardPage').default, [SCREENS.WORKSPACE.DISTANCE_RATES]: () => require('../../../../pages/workspace/distanceRates/PolicyDistanceRatesPage').default, } satisfies Screens; diff --git a/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardListPage.tsx b/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardListPage.tsx new file mode 100644 index 000000000000..36aee07ab6b5 --- /dev/null +++ b/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardListPage.tsx @@ -0,0 +1,173 @@ +import {useFocusEffect} from '@react-navigation/native'; +import type {StackScreenProps} from '@react-navigation/stack'; +import React, {useCallback, useMemo} from 'react'; +import type {ListRenderItemInfo} from 'react-native'; +import {FlatList, View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; +import {useOnyx} from 'react-native-onyx'; +import Button from '@components/Button'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import * as Expensicons from '@components/Icon/Expensicons'; +import * as Illustrations from '@components/Icon/Illustrations'; +import OfflineWithFeedback from '@components/OfflineWithFeedback'; +import {PressableWithoutFeedback} from '@components/Pressable'; +import ScreenWrapper from '@components/ScreenWrapper'; +import useLocalize from '@hooks/useLocalize'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useThemeStyles from '@hooks/useThemeStyles'; +import localeCompare from '@libs/LocaleCompare'; +import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; +import Navigation from '@navigation/Navigation'; +import type {FullScreenNavigatorParamList} from '@navigation/types'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type SCREENS from '@src/SCREENS'; +import type {Card, WorkspaceCardsList} from '@src/types/onyx'; +import WorkspaceCardListHeader from './WorkspaceCardListHeader'; +import WorkspaceCardListRow from './WorkspaceCardListRow'; + +type WorkspaceExpensifyCardListPageProps = {route: StackScreenProps['route']}; + +// TODO: remove this const altogether and take the card data from component prop when Onyx data is available +const mockedCards: OnyxEntry = { + test1: { + // @ts-expect-error TODO: change cardholder to accountID + cardholder: {accountID: 1, lastName: 'Smith', firstName: 'Bob', displayName: 'Bob Smith'}, + nameValuePairs: { + unapprovedExpenseLimit: 1000, + cardTitle: 'Test 1', + }, + lastFourPAN: '1234', + }, + test2: { + // @ts-expect-error TODO: change cardholder to accountID + cardholder: {accountID: 2, lastName: 'Miller', firstName: 'Alex', displayName: 'Alex Miller'}, + nameValuePairs: { + unapprovedExpenseLimit: 2000, + cardTitle: 'Test 2', + }, + lastFourPAN: '1234', + }, + test3: { + // @ts-expect-error TODO: change cardholder to accountID + cardholder: {accountID: 3, lastName: 'Brown', firstName: 'Kevin', displayName: 'Kevin Brown'}, + nameValuePairs: { + unapprovedExpenseLimit: 3000, + cardTitle: 'Test 3', + }, + lastFourPAN: '1234', + }, +}; + +function WorkspaceExpensifyCardListPage({route}: WorkspaceExpensifyCardListPageProps) { + const {shouldUseNarrowLayout} = useResponsiveLayout(); + const {translate} = useLocalize(); + const styles = useThemeStyles(); + + const policyID = route.params.policyID; + const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); + + const policyCurrency = useMemo(() => policy?.outputCurrency ?? CONST.CURRENCY.USD, [policy]); + + // TODO: uncomment the code line below to use cardsList data from Onyx when it's supported + // const [cardsList] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${policyID}_${CONST.EXPENSIFY_CARD.BANK}`); + const cardsList = mockedCards; + + const fetchExpensifyCards = useCallback(() => { + // TODO: uncomment when OpenPolicyExpensifyCardsPage API call is supported + // Policy.openPolicyExpensifyCardsPage(policyID); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [policyID]); + + useFocusEffect(fetchExpensifyCards); + + const sortedCards = useMemo( + () => + Object.values(cardsList ?? {}).sort((a, b) => { + // @ts-expect-error TODO: change cardholder to accountID and get personal details with it + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + const aName = PersonalDetailsUtils.getDisplayNameOrDefault(a.cardholder ?? {}); + // @ts-expect-error TODO: change cardholder to accountID and get personal details with it + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + const bName = PersonalDetailsUtils.getDisplayNameOrDefault(b.cardholder ?? {}); + return localeCompare(aName, bName); + }), + [cardsList], + ); + + const getHeaderButtons = () => ( + +