Skip to content
Merged
2 changes: 1 addition & 1 deletion src/components/FeedSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ function FeedSelector({onFeedSelect, CardFeedIcon, feedName, supportingText, sho

<View style={styles.flex1}>
<View style={[styles.flexRow, styles.gap1, styles.alignItemsCenter]}>
<CaretWrapper style={styles.flex1}>
<CaretWrapper>
<Text
numberOfLines={1}
style={[styles.textStrong, styles.flexShrink1]}
Expand Down
29 changes: 18 additions & 11 deletions src/components/SelectionList/ListItem/BaseListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ function BaseListItem<TItem extends ListItem>({
return rightHandSideComponent;
};

const shouldShowCheckmark = !canSelectMultiple && !!item.isSelected && !rightHandSideComponent && shouldUseDefaultRightHandSideCheckmark;

const shouldShowRBRIndicator = (!item.isSelected || !!item.canShowSeveralIndicators) && !!item.brickRoadIndicator && shouldDisplayRBR;

const shouldShowHiddenCheckmark = shouldShowRBRIndicator && !shouldShowCheckmark;

return (
<OfflineWithFeedback
onClose={() => onDismissError(item)}
Expand Down Expand Up @@ -136,9 +142,19 @@ function BaseListItem<TItem extends ListItem>({
>
{typeof children === 'function' ? children(hovered) : children}

{!canSelectMultiple && !!item.isSelected && !rightHandSideComponent && shouldUseDefaultRightHandSideCheckmark && (
{shouldShowRBRIndicator && (
<View style={[styles.alignItemsCenter, styles.justifyContentCenter, styles.ml3]}>
<Icon
testID={CONST.DOT_INDICATOR_TEST_ID}
src={Expensicons.DotIndicator}
fill={item.brickRoadIndicator === CONST.BRICK_ROAD_INDICATOR_STATUS.INFO ? theme.iconSuccessFill : theme.danger}
/>
</View>
)}

{(shouldShowCheckmark || shouldShowHiddenCheckmark) && (
<View
style={[styles.flexRow, styles.alignItemsCenter, styles.ml3]}
style={[styles.flexRow, styles.alignItemsCenter, styles.ml3, shouldShowHiddenCheckmark ? styles.opacity0 : undefined]}
accessible={false}
>
<View>
Expand All @@ -149,15 +165,6 @@ function BaseListItem<TItem extends ListItem>({
</View>
</View>
)}
{(!item.isSelected || !!item.canShowSeveralIndicators) && !!item.brickRoadIndicator && shouldDisplayRBR && (
<View style={[styles.alignItemsCenter, styles.justifyContentCenter]}>
<Icon
testID={CONST.DOT_INDICATOR_TEST_ID}
src={Expensicons.DotIndicator}
fill={item.brickRoadIndicator === CONST.BRICK_ROAD_INDICATOR_STATUS.INFO ? theme.iconSuccessFill : theme.danger}
/>
</View>
)}

{rightHandSideComponentRender()}
</View>
Expand Down
9 changes: 7 additions & 2 deletions src/hooks/useCompanyCards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ import useOnyx from './useOnyx';

type CardFeedType = ValueOf<typeof CONST.COMPANY_CARDS.FEED_TYPE>;

type UseCompanyCardsProps = {
policyID: string | undefined;
feedName?: CompanyCardFeedWithDomainID;
};

type UsCompanyCardsResult = Partial<{
cardFeedType: CardFeedType;
bankName: CompanyCardFeed;
Expand All @@ -31,11 +36,11 @@ type UsCompanyCardsResult = Partial<{
};
};

function useCompanyCards(policyID?: string): UsCompanyCardsResult {
function useCompanyCards({policyID, feedName: feedNameProp}: UseCompanyCardsProps): UsCompanyCardsResult {
const [lastSelectedFeed, lastSelectedFeedMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${policyID}`, {canBeMissing: true});
const [allCardFeeds, allCardFeedsMetadata] = useCardFeeds(policyID);

const feedName = getSelectedFeed(lastSelectedFeed, allCardFeeds);
const feedName = feedNameProp ?? getSelectedFeed(lastSelectedFeed, allCardFeeds);
const bankName = feedName ? getCompanyCardFeed(feedName) : undefined;

const [cardsList, cardListMetadata] = useCardsList(feedName);
Expand Down
20 changes: 16 additions & 4 deletions src/libs/actions/CompanyCards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,13 @@ function deleteWorkspaceCompanyCardFeed(policyID: string, domainOrWorkspaceAccou
API.write(WRITE_COMMANDS.DELETE_COMPANY_CARD_FEED, parameters, {optimisticData, successData, failureData});
}

function assignWorkspaceCompanyCard(policy: OnyxEntry<Policy>, domainOrWorkspaceAccountID: number, translate: LocaleContextProps['translate'], data?: Partial<AssignCardData>) {
function assignWorkspaceCompanyCard(
policy: OnyxEntry<Policy>,
domainOrWorkspaceAccountID: number,
translate: LocaleContextProps['translate'],
feed: CompanyCardFeedWithDomainID,
data?: Partial<AssignCardData>,
) {
if (!data || !policy?.id) {
return;
}
Expand All @@ -318,6 +324,8 @@ function assignWorkspaceCompanyCard(policy: OnyxEntry<Policy>, domainOrWorkspace
const optimisticCardAssignedReportAction = ReportUtils.buildOptimisticCardAssignedReportAction(assigneeDetails?.accountID ?? CONST.DEFAULT_NUMBER_ID);

const failedCardAssignment: FailedCompanyCardAssignment = {
domainOrWorkspaceAccountID,
feed,
cardholder,
cardName,
cardNumber: encryptedCardNumber,
Expand Down Expand Up @@ -373,7 +381,7 @@ function assignWorkspaceCompanyCard(policy: OnyxEntry<Policy>, domainOrWorkspace
},
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.FAILED_COMPANY_CARDS_ASSIGNMENTS}${domainOrWorkspaceAccountID}`,
key: `${ONYXKEYS.COLLECTION.FAILED_COMPANY_CARDS_ASSIGNMENTS}${domainOrWorkspaceAccountID}_${feed}`,
value: {
[encryptedCardNumber]: failedCardAssignment,
},
Expand Down Expand Up @@ -464,8 +472,12 @@ function unassignWorkspaceCompanyCard(domainOrWorkspaceAccountID: number, bankNa
API.write(WRITE_COMMANDS.UNASSIGN_COMPANY_CARD, parameters, onyxData);
}

function resetFailedWorkspaceCompanyCardAssignment(domainOrWorkspaceAccountID: number, cardNumber: string) {
Onyx.merge(`${ONYXKEYS.COLLECTION.FAILED_COMPANY_CARDS_ASSIGNMENTS}${domainOrWorkspaceAccountID}`, {
function resetFailedWorkspaceCompanyCardAssignment(domainOrWorkspaceAccountID: number, feed: CompanyCardFeedWithDomainID | undefined, cardNumber: string) {
if (!feed) {
return;
}

Onyx.merge(`${ONYXKEYS.COLLECTION.FAILED_COMPANY_CARDS_ASSIGNMENTS}${domainOrWorkspaceAccountID}_${feed}`, {
[cardNumber]: null,
});
}
Expand Down
39 changes: 23 additions & 16 deletions src/pages/workspace/WorkspaceInitialPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import OfflineWithFeedback from '@components/OfflineWithFeedback';
import ScreenWrapper from '@components/ScreenWrapper';
import ScrollView from '@components/ScrollView';
import Text from '@components/Text';
import useCardFeeds from '@hooks/useCardFeeds';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useGetReceiptPartnersIntegrationData from '@hooks/useGetReceiptPartnersIntegrationData';
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
Expand All @@ -30,7 +29,6 @@ import {confirmReadyToOpenApp} from '@libs/actions/App';
import {isConnectionInProgress} from '@libs/actions/connections';
import {shouldShowQBOReimbursableExportDestinationAccountError} from '@libs/actions/connections/QuickbooksOnline';
import {clearErrors, openPolicyInitialPage, removeWorkspace} from '@libs/actions/Policy/Policy';
import {checkIfFeedConnectionIsBroken, flatAllCardsList, getCompanyFeeds} from '@libs/CardUtils';
import {convertToDisplayString} from '@libs/CurrencyUtils';
import Navigation from '@libs/Navigation/Navigation';
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
Expand Down Expand Up @@ -58,6 +56,7 @@ import type {PendingAction} from '@src/types/onyx/OnyxCommon';
import type {PolicyFeatureName} from '@src/types/onyx/Policy';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import type IconAsset from '@src/types/utils/IconAsset';
import useHasWorkspaceCompanyCardErrors from './companyCards/hooks/useHasWorkspaceCompanyCardErrors';
import type {WithPolicyAndFullscreenLoadingProps} from './withPolicyAndFullscreenLoading';
import withPolicyAndFullscreenLoading from './withPolicyAndFullscreenLoading';

Expand Down Expand Up @@ -113,17 +112,11 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac
] as const);

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);
const isFocused = useIsFocused();
const [cardFeeds] = useCardFeeds(policy?.id);
const [allFeedsCards] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}`, {canBeMissing: true});
const [connectionSyncProgress] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS}${policy?.id}`, {canBeMissing: true});
const [currentUserLogin] = useOnyx(ONYXKEYS.SESSION, {selector: emailSelector, canBeMissing: false});
const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${route.params?.policyID}`, {canBeMissing: true});
const cardsDomainIDs = Object.values(getCompanyFeeds(cardFeeds))
.map((data) => data.domainID)
.filter((domainID): domainID is number => !!domainID);
const {login, accountID} = useCurrentUserPersonalDetails();
const hasSyncError = shouldShowSyncError(policy, isConnectionInProgress(connectionSyncProgress, policy));
const {shouldShowEnterCredentialsError} = useGetReceiptPartnersIntegrationData(policy?.id);
Expand Down Expand Up @@ -210,6 +203,8 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac

const [highlightedFeature, setHighlightedFeature] = useState<string | undefined>(undefined);

const hasCompanyCardFeedError = useHasWorkspaceCompanyCardErrors({policyID});

const workspaceMenuItems: WorkspaceMenuItem[] = useMemo(() => {
const protectedMenuItems: WorkspaceMenuItem[] = [];

Expand Down Expand Up @@ -330,14 +325,12 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac
}

if (featureStates?.[CONST.POLICY.MORE_FEATURES.ARE_COMPANY_CARDS_ENABLED]) {
const hasBrokenFeedConnection = checkIfFeedConnectionIsBroken(flatAllCardsList(allFeedsCards, workspaceAccountID, cardsDomainIDs));

protectedMenuItems.push({
translationKey: 'workspace.common.companyCards',
icon: expensifyIcons.CreditCard,
action: singleExecution(waitForNavigate(() => Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(policyID)))),
screenName: SCREENS.WORKSPACE.COMPANY_CARDS,
brickRoadIndicator: hasBrokenFeedConnection ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined,
brickRoadIndicator: hasCompanyCardFeedError ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined,
highlighted: highlightedFeature === CONST.POLICY.MORE_FEATURES.ARE_COMPANY_CARDS_ENABLED,
});
}
Expand Down Expand Up @@ -391,7 +384,23 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac

return menuItems;
}, [
expensifyIcons,
expensifyIcons.Document,
expensifyIcons.Gear,
expensifyIcons.Building,
expensifyIcons.Users,
expensifyIcons.Sync,
expensifyIcons.Receipt,
expensifyIcons.Folder,
expensifyIcons.Tag,
expensifyIcons.Coins,
expensifyIcons.Workflows,
expensifyIcons.Feed,
expensifyIcons.Car,
expensifyIcons.LuggageWithLines,
expensifyIcons.ExpensifyCard,
expensifyIcons.CreditCard,
expensifyIcons.CalendarSolid,
expensifyIcons.InvoiceGeneric,
singleExecution,
waitForNavigate,
featureStates,
Expand All @@ -402,11 +411,9 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac
policyID,
hasSyncError,
highlightedFeature,
hasPolicyCategoryError,
shouldShowEnterCredentialsError,
allFeedsCards,
workspaceAccountID,
cardsDomainIDs,
hasPolicyCategoryError,
hasCompanyCardFeedError,
]);

// We only update feature states if they aren't pending.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,17 @@ import ScreenWrapper from '@components/ScreenWrapper';
import SelectionList from '@components/SelectionList';
import RadioListItem from '@components/SelectionList/ListItem/RadioListItem';
import type {ListItem} from '@components/SelectionList/types';
import useCardFeeds from '@hooks/useCardFeeds';
import type {CombinedCardFeed, CompanyCardFeedWithDomainID} from '@hooks/useCardFeeds';
import {useCompanyCardFeedIcons} from '@hooks/useCompanyCardIcons';
import useCompanyCards from '@hooks/useCompanyCards';
import useIsBlockedToAddFeed from '@hooks/useIsBlockedToAddFeed';
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import usePolicy from '@hooks/usePolicy';
import useThemeIllustrations from '@hooks/useThemeIllustrations';
import useThemeStyles from '@hooks/useThemeStyles';
import {
checkIfFeedConnectionIsBroken,
filterInactiveCards,
getCardFeedIcon,
getCompanyFeeds,
getCustomOrFormattedFeedName,
getDomainOrWorkspaceAccountID,
getPlaidInstitutionIconUrl,
getSelectedFeed,
} from '@libs/CardUtils';
import {getCardFeedIcon, getCustomOrFormattedFeedName, getPlaidInstitutionIconUrl} from '@libs/CardUtils';
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
import type {SettingsNavigatorParamList} from '@libs/Navigation/types';
import Navigation from '@navigation/Navigation';
Expand All @@ -40,6 +31,7 @@ import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
import type {CompanyCardFeed} from '@src/types/onyx';
import useCompanyCardFeedErrors from './hooks/useCardFeedErrors';

type CardFeedListItem = ListItem & {
/** Combined feed key */
Expand All @@ -54,41 +46,36 @@ type WorkspaceCompanyCardFeedSelectorPageProps = PlatformStackScreenProps<Settin
function WorkspaceCompanyCardFeedSelectorPage({route}: WorkspaceCompanyCardFeedSelectorPageProps) {
const {policyID} = route.params;
const policy = usePolicy(policyID);
const workspaceAccountID = policy?.workspaceAccountID ?? CONST.DEFAULT_NUMBER_ID;

const {translate} = useLocalize();
const [allDomains] = useOnyx(ONYXKEYS.COLLECTION.DOMAIN, {canBeMissing: false});
const styles = useThemeStyles();
const illustrations = useThemeIllustrations();
const companyCardFeedIcons = useCompanyCardFeedIcons();
const [cardFeeds] = useCardFeeds(policyID);
const [allFeedsCards] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}`, {canBeMissing: false});
const [lastSelectedFeed] = useOnyx(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${policyID}`, {canBeMissing: true});
const selectedFeed = getSelectedFeed(lastSelectedFeed, cardFeeds);
const companyFeeds = getCompanyFeeds(cardFeeds);
const {isBlockedToAddNewFeeds} = useIsBlockedToAddFeed(policyID);
const icons = useMemoizedLazyExpensifyIcons(['Plus']);

const feeds: CardFeedListItem[] = (Object.entries(companyFeeds) as Array<[CompanyCardFeedWithDomainID, CombinedCardFeed]>).map(([key, feedSettings]) => {
const filteredFeedCards = filterInactiveCards(
allFeedsCards?.[`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${getDomainOrWorkspaceAccountID(workspaceAccountID, feedSettings)}_${feedSettings.feed}`],
);
const isFeedConnectionBroken = checkIfFeedConnectionIsBroken(filteredFeedCards);
const {companyCardFeeds, feedName: selectedFeedName} = useCompanyCards({policyID});
const {getCardFeedErrors} = useCompanyCardFeedErrors({policyID, feedName: selectedFeedName});

const feeds: CardFeedListItem[] = (Object.entries(companyCardFeeds ?? {}) as Array<[CompanyCardFeedWithDomainID, CombinedCardFeed]>).map(([feedName, feedSettings]) => {
const plaidUrl = getPlaidInstitutionIconUrl(feedSettings.feed);
const domain = allDomains?.[`${ONYXKEYS.COLLECTION.DOMAIN}${feedSettings.domainID}`];
const domainName = domain?.email ? Str.extractEmailDomain(domain.email) : undefined;

const {shouldShowRBR} = getCardFeedErrors(feedName);

return {
value: key,
value: feedName,
feed: feedSettings.feed,
alternateText: domainName ?? policy?.name,
text: getCustomOrFormattedFeedName(feedSettings.feed, feedSettings.customFeedName),
keyForList: key,
isSelected: key === selectedFeed,
keyForList: feedName,
isSelected: feedName === selectedFeedName,
isDisabled: feedSettings.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE,
pendingAction: feedSettings.pendingAction,
brickRoadIndicator: isFeedConnectionBroken ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined,
canShowSeveralIndicators: isFeedConnectionBroken,
brickRoadIndicator: shouldShowRBR ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined,
canShowSeveralIndicators: shouldShowRBR,
leftElement: plaidUrl ? (
<PlaidCardFeedIcon
plaidUrl={plaidUrl}
Expand Down Expand Up @@ -143,7 +130,7 @@ function WorkspaceCompanyCardFeedSelectorPage({route}: WorkspaceCompanyCardFeedS
onSelectRow={selectFeed}
data={feeds}
alternateNumberOfSupportedLines={2}
initiallyFocusedItemKey={selectedFeed}
initiallyFocusedItemKey={selectedFeedName}
addBottomSafeAreaPadding
listFooterContent={
<MenuItem
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ function WorkspaceCompanyCardsPage({route}: WorkspaceCompanyCardsPageProps) {
selectedFeed,
bankName,
onyxMetadata: {allCardFeedsMetadata},
} = useCompanyCards(policyID);
} = useCompanyCards({policyID});
const [, cardsListMetadata] = useCardsList(feedName);

const isInitiallyLoadingFeeds = isLoadingOnyxValue(allCardFeedsMetadata);
Expand Down
Loading
Loading