From 27c0427950a9a81987605e0f9959749a007c008a Mon Sep 17 00:00:00 2001 From: VickyStash Date: Thu, 8 Aug 2024 14:06:09 +0200 Subject: [PATCH 1/9] Enable OpenPolicyExpensifyCardsPage API call; Use workspaceAccountID instead of policyID in cards_ and sharedNVP_private_expensifyCardSettings_ onyx keys; TS fixes --- src/CONST.ts | 2 +- src/ONYXKEYS.ts | 2 +- src/languages/en.ts | 4 +- src/languages/es.ts | 4 +- src/libs/actions/Card.ts | 34 +++--- .../workspace/WorkspaceMoreFeaturesPage.tsx | 5 +- .../reconciliation/CardReconciliationPage.tsx | 2 +- .../ReconciliationAccountSettingsPage.tsx | 3 +- .../expensifyCard/WorkspaceCardListHeader.tsx | 26 ++-- .../expensifyCard/WorkspaceCardListRow.tsx | 6 +- .../WorkspaceCardSettingsPage.tsx | 3 +- .../WorkspaceEditCardLimitPage.tsx | 30 ++--- .../WorkspaceEditCardLimitTypePage.tsx | 21 +--- .../WorkspaceEditCardNamePage.tsx | 18 +-- .../WorkspaceExpensifyCardDetailsPage.tsx | 32 ++--- .../WorkspaceExpensifyCardListPage.tsx | 115 ++++++------------ .../WorkspaceExpensifyCardPage.tsx | 3 +- .../WorkspaceSettlementAccountPage.tsx | 3 +- .../WorkspaceSettlementFrequencyPage.tsx | 5 +- .../members/WorkspaceMemberDetailsPage.tsx | 113 +---------------- src/types/onyx/ExpensifyCardSettings.ts | 2 +- src/types/onyx/Policy.ts | 3 + 22 files changed, 125 insertions(+), 311 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 8adb5568e0fb..e15b464dbc27 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -5368,7 +5368,7 @@ const CONST = { WORKSPACE_CARDS_LIST_LABEL_TYPE: { CURRENT_BALANCE: 'currentBalance', REMAINING_LIMIT: 'remainingLimit', - CASH_BACK: 'cashBack', + CASH_BACK: 'earnedCashback', }, EXCLUDE_FROM_LAST_VISITED_PATH: [SCREENS.NOT_FOUND, SCREENS.SAML_SIGN_IN, SCREENS.VALIDATE_LOGIN] as string[], diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 40ab87055ca8..19fe7d670bcf 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -463,7 +463,7 @@ const ONYXKEYS = { * Stores the card list for a given fundID and feed in the format: card__ * So for example: card_12345_Expensify Card */ - WORKSPACE_CARDS_LIST: 'card_', + WORKSPACE_CARDS_LIST: 'cards_', /** Stores which connection is set up to use Continuous Reconciliation */ SHARED_NVP_EXPENSIFY_CARD_CONTINUOUS_RECONCILIATION_CONNECTION: 'sharedNVP_expensifyCard_continuousReconciliationConnection_', diff --git a/src/languages/en.ts b/src/languages/en.ts index 265965ef7abd..3c5abbaef82f 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2711,8 +2711,8 @@ export default { requestLimitIncrease: 'Request limit increase', remainingLimitDescription: 'We consider a number of factors when calculating your remaining limit: your tenure as a customer, the business-related information you provided during signup, and the available cash in your business bank account. Your remaining limit can fluctuate on a daily basis.', - cashBack: 'Cash back', - cashBackDescription: 'Cash back balance is based on settled monthly Expensify Card spend across your workspace.', + earnedCashback: 'Cash back', + earnedCashbackDescription: 'Cash back balance is based on settled monthly Expensify Card spend across your workspace.', issueNewCard: 'Issue new card', finishSetup: 'Finish setup', chooseBankAccount: 'Choose bank account', diff --git a/src/languages/es.ts b/src/languages/es.ts index b2e1da9ed224..79de69aeb3aa 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2761,8 +2761,8 @@ export default { requestLimitIncrease: 'Solicitar aumento de límite', remainingLimitDescription: 'A la hora de calcular tu límite restante, tenemos en cuenta una serie de factores: su antigüedad como cliente, la información relacionada con tu negocio que nos facilitaste al darte de alta y el efectivo disponible en tu cuenta bancaria comercial. Tu límite restante puede fluctuar a diario.', - cashBack: 'Reembolso', - cashBackDescription: 'El saldo de devolución se basa en el gasto mensual realizado con la tarjeta Expensify en tu espacio de trabajo.', + earnedCashback: 'Reembolso', + earnedCashbackDescription: 'El saldo de devolución se basa en el gasto mensual realizado con la tarjeta Expensify en tu espacio de trabajo.', issueNewCard: 'Emitir nueva tarjeta', finishSetup: 'Terminar configuración', chooseBankAccount: 'Elegir cuenta bancaria', diff --git a/src/libs/actions/Card.ts b/src/libs/actions/Card.ts index 205a8dc41bba..7f528f8c90bc 100644 --- a/src/libs/actions/Card.ts +++ b/src/libs/actions/Card.ts @@ -206,14 +206,14 @@ function revealVirtualCardDetails(cardID: number): Promise }); } -function updateSettlementFrequency(policyID: string, frequency: ValueOf) { +function updateSettlementFrequency(workspaceAccountID: number, frequency: ValueOf) { // TODO: remove this code when the API is ready if (frequency === CONST.EXPENSIFY_CARD.FREQUENCY_SETTING.DAILY) { - Onyx.merge(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_EXPENSIFY_CARD_SETTINGS}${policyID}`, { + Onyx.merge(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_EXPENSIFY_CARD_SETTINGS}${workspaceAccountID}`, { monthlySettlementDate: null, }); } else { - Onyx.merge(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_EXPENSIFY_CARD_SETTINGS}${policyID}`, { + Onyx.merge(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_EXPENSIFY_CARD_SETTINGS}${workspaceAccountID}`, { monthlySettlementDate: new Date(), }); } @@ -222,7 +222,7 @@ function updateSettlementFrequency(policyID: string, frequency: ValueOf bankAccountList?.[paymentBankAccountID], [paymentBankAccountID, bankAccountList]); diff --git a/src/pages/workspace/expensifyCard/WorkspaceCardListHeader.tsx b/src/pages/workspace/expensifyCard/WorkspaceCardListHeader.tsx index 0ec5b2fc18fa..414ae0903909 100644 --- a/src/pages/workspace/expensifyCard/WorkspaceCardListHeader.tsx +++ b/src/pages/workspace/expensifyCard/WorkspaceCardListHeader.tsx @@ -1,29 +1,29 @@ +import {useRoute} from '@react-navigation/native'; +import type {RouteProp} from '@react-navigation/native'; import React from 'react'; import {View} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; +import type {FullScreenNavigatorParamList} from '@navigation/types'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type SCREENS from '@src/SCREENS'; import WorkspaceCardsListLabel from './WorkspaceCardsListLabel'; -// TODO: remove when Onyx data is available -const mockedSettings = { - currentBalance: 5000, - remainingLimit: 3000, - cashBack: 2000, -}; - function WorkspaceCardListHeader() { const {shouldUseNarrowLayout, isMediumScreenWidth, isSmallScreenWidth} = useResponsiveLayout(); const styles = useThemeStyles(); const {translate} = useLocalize(); + const route = useRoute>(); + const policyID = route.params.policyID; const isLessThanMediumScreen = isMediumScreenWidth || isSmallScreenWidth; - // TODO: uncomment the code line below to use cardSettings data from Onyx when it's supported - // const [cardSettings] = useOnyx(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_EXPENSIFY_CARD_SETTINGS}${policyID}`); - const cardSettings = mockedSettings; + const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); + const [cardSettings] = useOnyx(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_EXPENSIFY_CARD_SETTINGS}${policy?.workspaceAccountID}`); return ( @@ -31,16 +31,16 @@ function WorkspaceCardListHeader() { diff --git a/src/pages/workspace/expensifyCard/WorkspaceCardListRow.tsx b/src/pages/workspace/expensifyCard/WorkspaceCardListRow.tsx index 009b289c9bb4..7bbeae3a167b 100644 --- a/src/pages/workspace/expensifyCard/WorkspaceCardListRow.tsx +++ b/src/pages/workspace/expensifyCard/WorkspaceCardListRow.tsx @@ -19,7 +19,7 @@ type WorkspacesListRowProps = { name: string; /** Cardholder personal details */ - cardholder: PersonalDetails; + cardholder?: PersonalDetails | null; /** Card limit */ limit: number; @@ -38,8 +38,8 @@ function WorkspaceCardListRow({limit, cardholder, lastFourPAN, name, currency}: diff --git a/src/pages/workspace/expensifyCard/WorkspaceCardSettingsPage.tsx b/src/pages/workspace/expensifyCard/WorkspaceCardSettingsPage.tsx index 399ff41d945b..6b4467bd9634 100644 --- a/src/pages/workspace/expensifyCard/WorkspaceCardSettingsPage.tsx +++ b/src/pages/workspace/expensifyCard/WorkspaceCardSettingsPage.tsx @@ -27,7 +27,8 @@ function WorkspaceCardSettingsPage({route}: WorkspaceCardSettingsPageProps) { const policyID = route.params?.policyID; const [bankAccountList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST); - const [cardSettings] = useOnyx(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_EXPENSIFY_CARD_SETTINGS}${policyID}`); + const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); + const [cardSettings] = useOnyx(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_EXPENSIFY_CARD_SETTINGS}${policy?.workspaceAccountID}`); const paymentBankAccountID = cardSettings?.paymentBankAccountID ?? ''; // const isMonthlySettlementAllowed = cardSettings?.isMonthlySettlementAllowed ?? true; diff --git a/src/pages/workspace/expensifyCard/WorkspaceEditCardLimitPage.tsx b/src/pages/workspace/expensifyCard/WorkspaceEditCardLimitPage.tsx index 30808b32e478..26c391c086f8 100644 --- a/src/pages/workspace/expensifyCard/WorkspaceEditCardLimitPage.tsx +++ b/src/pages/workspace/expensifyCard/WorkspaceEditCardLimitPage.tsx @@ -25,19 +25,6 @@ import INPUT_IDS from '@src/types/form/EditExpensifyCardLimitForm'; type ConfirmationWarningTranslationPaths = 'workspace.expensifyCard.smartLimitWarning' | 'workspace.expensifyCard.monthlyLimitWarning' | 'workspace.expensifyCard.fixedLimitWarning'; -// TODO: remove when Onyx data is available -const mockedCard = { - accountID: 885646, - availableSpend: 1000, - nameValuePairs: { - cardTitle: 'Test 1', - isVirtual: true, - limit: 2000, - limitType: CONST.EXPENSIFY_CARD.LIMIT_TYPES.SMART, - }, - lastFourPAN: '1234', -}; - type WorkspaceEditCardLimitPageProps = StackScreenProps; function WorkspaceEditCardLimitPage({route}: WorkspaceEditCardLimitPageProps) { @@ -47,11 +34,12 @@ function WorkspaceEditCardLimitPage({route}: WorkspaceEditCardLimitPageProps) { const styles = useThemeStyles(); const [isConfirmModalVisible, setIsConfirmModalVisible] = useState(false); - const [cardsList] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${policyID}_${CONST.EXPENSIFY_CARD.BANK}`); - const card = cardsList?.[cardID] ?? mockedCard; + const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); + const [cardsList] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${policy?.workspaceAccountID}_${CONST.EXPENSIFY_CARD.BANK}`); + const card = cardsList?.[cardID]; const getPromptTextKey = useMemo((): ConfirmationWarningTranslationPaths => { - switch (card.nameValuePairs?.limitType) { + switch (card?.nameValuePairs?.limitType) { case CONST.EXPENSIFY_CARD.LIMIT_TYPES.SMART: return 'workspace.expensifyCard.smartLimitWarning'; case CONST.EXPENSIFY_CARD.LIMIT_TYPES.FIXED: @@ -61,19 +49,19 @@ function WorkspaceEditCardLimitPage({route}: WorkspaceEditCardLimitPageProps) { default: return 'workspace.expensifyCard.fixedLimitWarning'; } - }, [card.nameValuePairs?.limitType]); + }, [card?.nameValuePairs?.limitType]); const updateCardLimit = (newLimit: string) => { setIsConfirmModalVisible(false); - Card.updateExpensifyCardLimit(policyID, Number(cardID), Number(newLimit) * 100, card.nameValuePairs?.limit); + Card.updateExpensifyCardLimit(policy?.workspaceAccountID ?? -1, Number(cardID), Number(newLimit) * 100, card?.nameValuePairs?.limit); Navigation.goBack(); }; const submit = (values: FormOnyxValues) => { - const currentLimit = card.nameValuePairs?.limit ?? 0; - const currentSpend = currentLimit - (card.availableSpend ?? 0); + const currentLimit = card?.nameValuePairs?.limit ?? 0; + const currentSpend = currentLimit - (card?.availableSpend ?? 0); const newLimit = Number(values[INPUT_IDS.LIMIT]) * 100; const newAvailableSpend = newLimit - currentSpend; @@ -129,7 +117,7 @@ function WorkspaceEditCardLimitPage({route}: WorkspaceEditCardLimitPageProps) { <> ; function WorkspaceEditCardLimitTypePage({route}: WorkspaceEditCardLimitTypePageProps) { @@ -40,13 +27,13 @@ function WorkspaceEditCardLimitTypePage({route}: WorkspaceEditCardLimitTypePageP const {translate} = useLocalize(); const styles = useThemeStyles(); - const [cardsList] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${policyID}_${CONST.EXPENSIFY_CARD.BANK}`); const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); + const [cardsList] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${policy?.workspaceAccountID}_${CONST.EXPENSIFY_CARD.BANK}`); - const card = cardsList?.[cardID] ?? mockedCard; + const card = cardsList?.[cardID]; const areApprovalsConfigured = !isEmptyObject(policy?.approver) && policy?.approvalMode !== CONST.POLICY.APPROVAL_MODE.OPTIONAL; const defaultLimitType = areApprovalsConfigured ? CONST.EXPENSIFY_CARD.LIMIT_TYPES.SMART : CONST.EXPENSIFY_CARD.LIMIT_TYPES.MONTHLY; - const initialLimitType = card.nameValuePairs?.limitType ?? defaultLimitType; + const initialLimitType = card?.nameValuePairs?.limitType ?? defaultLimitType; const promptTranslationKey = initialLimitType === CONST.EXPENSIFY_CARD.LIMIT_TYPES.MONTHLY ? 'workspace.expensifyCard.changeCardMonthlyLimitTypeWarning' @@ -136,7 +123,7 @@ function WorkspaceEditCardLimitTypePage({route}: WorkspaceEditCardLimitTypePageP isVisible={isConfirmModalVisible} onConfirm={updateCardLimitType} onCancel={() => setIsConfirmModalVisible(false)} - prompt={translate(promptTranslationKey, CurrencyUtils.convertToDisplayString(card.nameValuePairs?.limit, CONST.CURRENCY.USD))} + prompt={translate(promptTranslationKey, CurrencyUtils.convertToDisplayString(card?.nameValuePairs?.limit, CONST.CURRENCY.USD))} confirmText={translate('workspace.expensifyCard.changeLimitType')} cancelText={translate('common.cancel')} danger diff --git a/src/pages/workspace/expensifyCard/WorkspaceEditCardNamePage.tsx b/src/pages/workspace/expensifyCard/WorkspaceEditCardNamePage.tsx index 9986f5d39596..7fe92d6d0afc 100644 --- a/src/pages/workspace/expensifyCard/WorkspaceEditCardNamePage.tsx +++ b/src/pages/workspace/expensifyCard/WorkspaceEditCardNamePage.tsx @@ -20,19 +20,6 @@ import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import INPUT_IDS from '@src/types/form/EditExpensifyCardNameForm'; -// TODO: remove when Onyx data is available -const mockedCard = { - accountID: 885646, - availableSpend: 1000, - nameValuePairs: { - cardTitle: 'Test 1', - isVirtual: true, - limit: 2000, - limitType: CONST.EXPENSIFY_CARD.LIMIT_TYPES.SMART, - }, - lastFourPAN: '1234', -}; - type WorkspaceEditCardNamePageProps = StackScreenProps; function WorkspaceEditCardNamePage({route}: WorkspaceEditCardNamePageProps) { @@ -42,8 +29,9 @@ function WorkspaceEditCardNamePage({route}: WorkspaceEditCardNamePageProps) { const {inputCallbackRef} = useAutoFocusInput(); const styles = useThemeStyles(); - const [cardsList] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${policyID}_${CONST.EXPENSIFY_CARD.BANK}`); - const card = cardsList?.[cardID] ?? mockedCard; + const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); + const [cardsList] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${policy?.workspaceAccountID}_${CONST.EXPENSIFY_CARD.BANK}`); + const card = cardsList?.[cardID]; // eslint-disable-next-line @typescript-eslint/no-unused-vars const submit = (values: FormOnyxValues) => { diff --git a/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardDetailsPage.tsx b/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardDetailsPage.tsx index c2545778e53c..794ef6099ca0 100644 --- a/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardDetailsPage.tsx +++ b/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardDetailsPage.tsx @@ -28,19 +28,6 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -// TODO: remove when Onyx data is available -const mockedCard = { - accountID: 885646, - availableSpend: 1000, - nameValuePairs: { - cardTitle: 'Test 1', - isVirtual: true, - limit: 2000, - limitType: CONST.EXPENSIFY_CARD.LIMIT_TYPES.SMART, - }, - lastFourPAN: '1234', -}; - type WorkspaceExpensifyCardDetailsPageProps = StackScreenProps; function WorkspaceExpensifyCardDetailsPage({route}: WorkspaceExpensifyCardDetailsPageProps) { @@ -52,15 +39,16 @@ function WorkspaceExpensifyCardDetailsPage({route}: WorkspaceExpensifyCardDetail const theme = useTheme(); const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); - const [cardsList] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${policyID}_${CONST.EXPENSIFY_CARD.BANK}`); + const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); + const [cardsList] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${policy?.workspaceAccountID}_${CONST.EXPENSIFY_CARD.BANK}`); - const card = cardsList?.[cardID] ?? mockedCard; - const cardholder = personalDetails?.[card.accountID ?? -1]; - const isVirtual = !!card.nameValuePairs?.isVirtual; - const formattedAvailableSpendAmount = CurrencyUtils.convertToDisplayString(card.availableSpend); - const formattedLimit = CurrencyUtils.convertToDisplayString(card.nameValuePairs?.limit); + const card = cardsList?.[cardID]; + const cardholder = personalDetails?.[card?.accountID ?? -1]; + const isVirtual = !!card?.nameValuePairs?.isVirtual; + const formattedAvailableSpendAmount = CurrencyUtils.convertToDisplayString(card?.availableSpend); + const formattedLimit = CurrencyUtils.convertToDisplayString(card?.nameValuePairs?.limit); const displayName = PersonalDetailsUtils.getDisplayNameOrDefault(cardholder); - const translationForLimitType = CardUtils.getTranslationKeyForLimitType(card.nameValuePairs?.limitType); + const translationForLimitType = CardUtils.getTranslationKeyForLimitType(card?.nameValuePairs?.limitType); const deactivateCard = () => { setIsDeactivateModalVisible(false); @@ -112,7 +100,7 @@ function WorkspaceExpensifyCardDetailsPage({route}: WorkspaceExpensifyCardDetail /> @@ -136,7 +124,7 @@ function WorkspaceExpensifyCardDetailsPage({route}: WorkspaceExpensifyCardDetail /> Navigation.navigate(ROUTES.WORKSPACE_EXPENSIFY_CARD_NAME.getRoute(policyID, cardID))} /> diff --git a/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardListPage.tsx b/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardListPage.tsx index 742a5c2a290e..1e52d003d070 100644 --- a/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardListPage.tsx +++ b/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardListPage.tsx @@ -1,9 +1,8 @@ import {useFocusEffect} from '@react-navigation/native'; -import type {StackScreenProps} from '@react-navigation/stack'; +import type {RouteProp} from '@react-navigation/native'; 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'; @@ -19,51 +18,18 @@ import localeCompare from '@libs/LocaleCompare'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import Navigation from '@navigation/Navigation'; import type {FullScreenNavigatorParamList} from '@navigation/types'; +import * as Policy from '@userActions/Policy/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -import type {Card, WorkspaceCardsList} from '@src/types/onyx'; +import type {Card} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import EmptyCardView from './EmptyCardView'; 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'}, - cardID: 1, - 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'}, - cardID: 2, - 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'}, - cardID: 3, - nameValuePairs: { - unapprovedExpenseLimit: 3000, - cardTitle: 'Test 3', - }, - lastFourPAN: '1234', - }, -}; +type WorkspaceExpensifyCardListPageProps = {route: RouteProp}; function WorkspaceExpensifyCardListPage({route}: WorkspaceExpensifyCardListPageProps) { const {shouldUseNarrowLayout} = useResponsiveLayout(); @@ -72,33 +38,27 @@ function WorkspaceExpensifyCardListPage({route}: WorkspaceExpensifyCardListPageP const policyID = route.params.policyID; const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); + const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); + const [cardsList] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${policy?.workspaceAccountID}_${CONST.EXPENSIFY_CARD.BANK}`); 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-compiler/react-compiler, react-hooks/exhaustive-deps + Policy.openPolicyExpensifyCardsPage(policyID); }, [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 ?? {}); + Object.values(cardsList ?? {}).sort((cardA: Card, cardB: Card) => { + const userA = personalDetails?.[cardA.accountID ?? '-1'] ?? {}; + const userB = personalDetails?.[cardB.accountID ?? '-1'] ?? {}; + const aName = PersonalDetailsUtils.getDisplayNameOrDefault(userA); + const bName = PersonalDetailsUtils.getDisplayNameOrDefault(userB); return localeCompare(aName, bName); }), - [cardsList], + [cardsList, personalDetails], ); const getHeaderButtons = () => ( @@ -121,30 +81,31 @@ function WorkspaceExpensifyCardListPage({route}: WorkspaceExpensifyCardListPageP ); - const renderItem = ({item, index}: ListRenderItemInfo) => ( - - Navigation.navigate(ROUTES.WORKSPACE_EXPENSIFY_CARD_DETAILS.getRoute(policyID, item.cardID.toString()))} + const renderItem = useCallback( + ({item, index}: ListRenderItemInfo) => ( + - - - + Navigation.navigate(ROUTES.WORKSPACE_EXPENSIFY_CARD_DETAILS.getRoute(policyID, item.cardID.toString()))} + > + + + + ), + [personalDetails, policyCurrency, policyID, styles], ); return ( @@ -163,7 +124,7 @@ function WorkspaceExpensifyCardListPage({route}: WorkspaceExpensifyCardListPageP {!shouldUseNarrowLayout && getHeaderButtons()} {shouldUseNarrowLayout && {getHeaderButtons()}} - {!isEmptyObject(cardsList) ? ( + {isEmptyObject(cardsList) ? ( ) : ( ) => { - Card.updateSettlementFrequency(policyID, value); + Card.updateSettlementFrequency(policy?.workspaceAccountID ?? -1, value); }; return ( diff --git a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx index a4a4f030d9c4..7635077d5fd3 100644 --- a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx +++ b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx @@ -1,6 +1,7 @@ import type {StackScreenProps} from '@react-navigation/stack'; import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {View} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import ExpensifyCardImage from '@assets/images/expensify-card.svg'; @@ -32,114 +33,13 @@ import variables from '@styles/variables'; import * as Card from '@userActions/Card'; import * as Member from '@userActions/Policy/Member'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -import type {PersonalDetails, PersonalDetailsList, WorkspaceCardsList} from '@src/types/onyx'; +import type {PersonalDetails, PersonalDetailsList} from '@src/types/onyx'; import type {ListItemType} from './WorkspaceMemberDetailsRoleSelectionModal'; import WorkspaceMemberDetailsRoleSelectionModal from './WorkspaceMemberDetailsRoleSelectionModal'; -// TODO: remove when Onyx data is available -const mockedCards: OnyxEntry = { - test1: { - accountID: 885646, - cardID: 1, - nameValuePairs: { - limit: 1000, - cardTitle: 'Test 1', - }, - lastFourPAN: '1234', - state: CONST.EXPENSIFY_CARD.STATE.OPEN, - bank: '', - availableSpend: 1, - domainName: '', - fraud: CONST.EXPENSIFY_CARD.FRAUD_TYPES.INDIVIDUAL, - }, - test2: { - accountID: 885646, - cardID: 2, - nameValuePairs: { - limit: 2000, - cardTitle: 'Test 2', - }, - lastFourPAN: '1234', - state: CONST.EXPENSIFY_CARD.STATE.OPEN, - bank: '', - availableSpend: 1, - domainName: '', - fraud: CONST.EXPENSIFY_CARD.FRAUD_TYPES.INDIVIDUAL, - }, - test3: { - accountID: 885646, - cardID: 3, - nameValuePairs: { - limit: 3000, - cardTitle: 'Test 3', - }, - lastFourPAN: '1234', - state: CONST.EXPENSIFY_CARD.STATE.OPEN, - bank: '', - availableSpend: 1, - domainName: '', - fraud: CONST.EXPENSIFY_CARD.FRAUD_TYPES.INDIVIDUAL, - }, - test4: { - accountID: 885646, - cardID: 3, - nameValuePairs: { - limit: 3000, - cardTitle: 'Test 3', - }, - lastFourPAN: '1234', - state: CONST.EXPENSIFY_CARD.STATE.OPEN, - bank: '', - availableSpend: 1, - domainName: '', - fraud: CONST.EXPENSIFY_CARD.FRAUD_TYPES.INDIVIDUAL, - }, - test5: { - accountID: 885646, - cardID: 3, - nameValuePairs: { - limit: 3000, - cardTitle: 'Test 3', - }, - lastFourPAN: '1234', - state: CONST.EXPENSIFY_CARD.STATE.OPEN, - bank: '', - availableSpend: 1, - domainName: '', - fraud: CONST.EXPENSIFY_CARD.FRAUD_TYPES.INDIVIDUAL, - }, - test6: { - accountID: 885646, - cardID: 3, - nameValuePairs: { - limit: 3000, - cardTitle: 'Test 3', - }, - lastFourPAN: '1234', - state: CONST.EXPENSIFY_CARD.STATE.OPEN, - bank: '', - availableSpend: 1, - domainName: '', - fraud: CONST.EXPENSIFY_CARD.FRAUD_TYPES.INDIVIDUAL, - }, - test7: { - accountID: 885646, - cardID: 3, - nameValuePairs: { - limit: 3000, - cardTitle: 'Test 3', - }, - lastFourPAN: '1234', - state: CONST.EXPENSIFY_CARD.STATE.OPEN, - bank: '', - availableSpend: 1, - domainName: '', - fraud: CONST.EXPENSIFY_CARD.FRAUD_TYPES.INDIVIDUAL, - }, -}; - type WorkspacePolicyOnyxProps = { /** Personal details of all users */ personalDetails: OnyxEntry; @@ -155,16 +55,13 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM const {translate} = useLocalize(); const StyleUtils = useStyleUtils(); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); + const [cardsList] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${policy?.workspaceAccountID}_${CONST.EXPENSIFY_CARD.BANK}`); const [isRemoveMemberConfirmModalVisible, setIsRemoveMemberConfirmModalVisible] = useState(false); const [isRoleSelectionModalVisible, setIsRoleSelectionModalVisible] = useState(false); const accountID = Number(route.params.accountID); const policyID = route.params.policyID; - // TODO: uncomment the code line below to use cardsList data from Onyx when it's supported - // const [cardList] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${policyID}_${CONST.EXPENSIFY_CARD.BANK}`); - const cardList = mockedCards; - const memberLogin = personalDetails?.[accountID]?.login ?? ''; const member = policy?.employeeList?.[memberLogin]; const prevMember = usePrevious(member); @@ -362,7 +259,7 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM {translate('walletPage.assignedCards')} - {Object.values(cardList ?? {}).map((card) => ( + {Object.values(cardsList ?? {}).map((card) => ( , 'generalSettings' | 'addWorkspaceRoom' | keyof ACHAccount >; From b451dc2ad675c2b414eb5ed8f8aeacc6a3003f4e Mon Sep 17 00:00:00 2001 From: VickyStash Date: Thu, 8 Aug 2024 17:53:02 +0200 Subject: [PATCH 2/9] Use correct field as a card limit --- src/libs/actions/Card.ts | 4 ++-- .../workspace/expensifyCard/WorkspaceEditCardLimitPage.tsx | 6 +++--- .../expensifyCard/WorkspaceEditCardLimitTypePage.tsx | 2 +- .../expensifyCard/WorkspaceExpensifyCardDetailsPage.tsx | 2 +- src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx | 2 +- src/types/onyx/Card.ts | 3 --- 6 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/libs/actions/Card.ts b/src/libs/actions/Card.ts index 7f528f8c90bc..7972775c568f 100644 --- a/src/libs/actions/Card.ts +++ b/src/libs/actions/Card.ts @@ -327,7 +327,7 @@ function updateExpensifyCardLimit(workspaceAccountID: number, cardID: number, ne value: { [cardID]: { nameValuePairs: { - limit: newLimit, + unapprovedExpenseLimit: newLimit, }, isLoading: true, errors: null, @@ -355,7 +355,7 @@ function updateExpensifyCardLimit(workspaceAccountID: number, cardID: number, ne value: { [cardID]: { nameValuePairs: { - limit: oldLimit, + unapprovedExpenseLimit: oldLimit, }, isLoading: false, errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'), diff --git a/src/pages/workspace/expensifyCard/WorkspaceEditCardLimitPage.tsx b/src/pages/workspace/expensifyCard/WorkspaceEditCardLimitPage.tsx index 26c391c086f8..bf1553441271 100644 --- a/src/pages/workspace/expensifyCard/WorkspaceEditCardLimitPage.tsx +++ b/src/pages/workspace/expensifyCard/WorkspaceEditCardLimitPage.tsx @@ -54,13 +54,13 @@ function WorkspaceEditCardLimitPage({route}: WorkspaceEditCardLimitPageProps) { const updateCardLimit = (newLimit: string) => { setIsConfirmModalVisible(false); - Card.updateExpensifyCardLimit(policy?.workspaceAccountID ?? -1, Number(cardID), Number(newLimit) * 100, card?.nameValuePairs?.limit); + Card.updateExpensifyCardLimit(policy?.workspaceAccountID ?? -1, Number(cardID), Number(newLimit) * 100, card?.nameValuePairs?.unapprovedExpenseLimit); Navigation.goBack(); }; const submit = (values: FormOnyxValues) => { - const currentLimit = card?.nameValuePairs?.limit ?? 0; + const currentLimit = card?.nameValuePairs?.unapprovedExpenseLimit ?? 0; const currentSpend = currentLimit - (card?.availableSpend ?? 0); const newLimit = Number(values[INPUT_IDS.LIMIT]) * 100; const newAvailableSpend = newLimit - currentSpend; @@ -117,7 +117,7 @@ function WorkspaceEditCardLimitPage({route}: WorkspaceEditCardLimitPageProps) { <> setIsConfirmModalVisible(false)} - prompt={translate(promptTranslationKey, CurrencyUtils.convertToDisplayString(card?.nameValuePairs?.limit, CONST.CURRENCY.USD))} + prompt={translate(promptTranslationKey, CurrencyUtils.convertToDisplayString(card?.nameValuePairs?.unapprovedExpenseLimit, CONST.CURRENCY.USD))} confirmText={translate('workspace.expensifyCard.changeLimitType')} cancelText={translate('common.cancel')} danger diff --git a/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardDetailsPage.tsx b/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardDetailsPage.tsx index 794ef6099ca0..3aa82079c370 100644 --- a/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardDetailsPage.tsx +++ b/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardDetailsPage.tsx @@ -46,7 +46,7 @@ function WorkspaceExpensifyCardDetailsPage({route}: WorkspaceExpensifyCardDetail const cardholder = personalDetails?.[card?.accountID ?? -1]; const isVirtual = !!card?.nameValuePairs?.isVirtual; const formattedAvailableSpendAmount = CurrencyUtils.convertToDisplayString(card?.availableSpend); - const formattedLimit = CurrencyUtils.convertToDisplayString(card?.nameValuePairs?.limit); + const formattedLimit = CurrencyUtils.convertToDisplayString(card?.nameValuePairs?.unapprovedExpenseLimit); const displayName = PersonalDetailsUtils.getDisplayNameOrDefault(cardholder); const translationForLimitType = CardUtils.getTranslationKeyForLimitType(card?.nameValuePairs?.limitType); diff --git a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx index 7635077d5fd3..2742033430ba 100644 --- a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx +++ b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx @@ -262,7 +262,7 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM {Object.values(cardsList ?? {}).map((card) => ( ; - /** Card spending limit */ - limit?: number; - /** User-defined nickname for the card */ cardTitle?: string; From 3d754566874458ebf3f9fc038e84cc0d316ca0f8 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Fri, 9 Aug 2024 10:24:44 +0200 Subject: [PATCH 3/9] Add card list page loading indication --- src/libs/actions/Policy/Policy.ts | 34 +++++++++++++++++-- .../WorkspaceExpensifyCardListPage.tsx | 8 ----- .../WorkspaceExpensifyCardPage.tsx | 30 ++++++++++++++-- src/types/onyx/ExpensifyCardSettings.ts | 3 ++ 4 files changed, 62 insertions(+), 13 deletions(-) diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index 88e6bbe59312..fb8b273d74ed 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -2036,15 +2036,45 @@ function openPolicyTaxesPage(policyID: string) { API.read(READ_COMMANDS.OPEN_POLICY_TAXES_PAGE, params); } -function openPolicyExpensifyCardsPage(policyID: string) { +function openPolicyExpensifyCardsPage(policyID: string, workspaceAccountID: number) { const authToken = NetworkStore.getAuthToken(); + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_EXPENSIFY_CARD_SETTINGS}${workspaceAccountID}`, + value: { + isLoading: true, + }, + }, + ]; + + const successData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_EXPENSIFY_CARD_SETTINGS}${workspaceAccountID}`, + value: { + isLoading: false, + }, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_EXPENSIFY_CARD_SETTINGS}${workspaceAccountID}`, + value: { + isLoading: false, + }, + }, + ]; + const params: OpenPolicyExpensifyCardsPageParams = { policyID, authToken, }; - API.read(READ_COMMANDS.OPEN_POLICY_EXPENSIFY_CARDS_PAGE, params); + API.read(READ_COMMANDS.OPEN_POLICY_EXPENSIFY_CARDS_PAGE, params, {optimisticData, successData, failureData}); } function openWorkspaceInvitePage(policyID: string, clientMemberEmails: string[]) { diff --git a/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardListPage.tsx b/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardListPage.tsx index 1e52d003d070..755396d36a54 100644 --- a/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardListPage.tsx +++ b/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardListPage.tsx @@ -1,4 +1,3 @@ -import {useFocusEffect} from '@react-navigation/native'; import type {RouteProp} from '@react-navigation/native'; import React, {useCallback, useMemo} from 'react'; import type {ListRenderItemInfo} from 'react-native'; @@ -18,7 +17,6 @@ import localeCompare from '@libs/LocaleCompare'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import Navigation from '@navigation/Navigation'; import type {FullScreenNavigatorParamList} from '@navigation/types'; -import * as Policy from '@userActions/Policy/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -43,12 +41,6 @@ function WorkspaceExpensifyCardListPage({route}: WorkspaceExpensifyCardListPageP const policyCurrency = useMemo(() => policy?.outputCurrency ?? CONST.CURRENCY.USD, [policy]); - const fetchExpensifyCards = useCallback(() => { - Policy.openPolicyExpensifyCardsPage(policyID); - }, [policyID]); - - useFocusEffect(fetchExpensifyCards); - const sortedCards = useMemo( () => Object.values(cardsList ?? {}).sort((cardA: Card, cardB: Card) => { diff --git a/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardPage.tsx b/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardPage.tsx index 343ee9209acc..17f3cd3b8079 100644 --- a/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardPage.tsx +++ b/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardPage.tsx @@ -1,8 +1,14 @@ +import {useFocusEffect} from '@react-navigation/native'; import type {StackScreenProps} from '@react-navigation/stack'; -import React from 'react'; +import React, {useCallback} from 'react'; +import {ActivityIndicator} from 'react-native'; import {useOnyx} from 'react-native-onyx'; +import useNetwork from '@hooks/useNetwork'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; import type {FullScreenNavigatorParamList} from '@libs/Navigation/types'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; +import * as Policy from '@userActions/Policy/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; @@ -13,10 +19,21 @@ type WorkspaceExpensifyCardPageProps = StackScreenProps { + Policy.openPolicyExpensifyCardsPage(policyID, policy?.workspaceAccountID ?? -1); + }, [policyID, policy?.workspaceAccountID]); + + const {isOffline} = useNetwork({onReconnect: fetchExpensifyCards}); + + useFocusEffect(fetchExpensifyCards); + const paymentBankAccountID = cardSettings?.paymentBankAccountID ?? 0; + const isLoading = !isOffline && (!cardSettings || cardSettings.isLoading); return ( - {/* After BE will be implemented we will probably want to have ActivityIndicator during fetch for cardsList */} - {paymentBankAccountID ? : } + {isLoading && ( + + )} + {!!paymentBankAccountID && !isLoading && } + {!paymentBankAccountID && !isLoading && } ); } diff --git a/src/types/onyx/ExpensifyCardSettings.ts b/src/types/onyx/ExpensifyCardSettings.ts index 01fe188681f3..e5846e1bcab6 100644 --- a/src/types/onyx/ExpensifyCardSettings.ts +++ b/src/types/onyx/ExpensifyCardSettings.ts @@ -19,6 +19,9 @@ type ExpensifyCardSettings = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** The bank account chosen for the card settlement */ paymentBankAccountID: number; + + /** Whether we are loading the data via the API */ + isLoading?: boolean; }>; export default ExpensifyCardSettings; From 7ba11bc02474878919ec74c3083eb987c8f9dcc9 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Fri, 9 Aug 2024 10:27:25 +0200 Subject: [PATCH 4/9] Fix console error --- src/components/FeatureList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/FeatureList.tsx b/src/components/FeatureList.tsx index b9ec94a9a9c8..88dfed67d0c6 100644 --- a/src/components/FeatureList.tsx +++ b/src/components/FeatureList.tsx @@ -117,7 +117,7 @@ function FeatureList({ ))} - {secondaryButtonText && ( + {!!secondaryButtonText && (