Skip to content

Commit

Permalink
Merge pull request Expensify#54425 from callstack-internal/VickyStash…
Browse files Browse the repository at this point in the history
…/bugfix/54379-delete-feed-offline

Improve feed removal flow offline
  • Loading branch information
mountiny authored Dec 26, 2024
2 parents 116dbd4 + d11c081 commit cdefab1
Show file tree
Hide file tree
Showing 11 changed files with 150 additions and 86 deletions.
27 changes: 12 additions & 15 deletions src/libs/CardUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,8 @@ function getEligibleBankAccountsForCard(bankAccountsList: OnyxEntry<BankAccountL
function sortCardsByCardholderName(cardsList: OnyxEntry<WorkspaceCardsList>, personalDetails: OnyxEntry<PersonalDetailsList>): Card[] {
const {cardList, ...cards} = cardsList ?? {};
return Object.values(cards).sort((cardA: Card, cardB: Card) => {
const userA = personalDetails?.[cardA.accountID ?? '-1'] ?? {};
const userB = personalDetails?.[cardB.accountID ?? '-1'] ?? {};
const userA = cardA.accountID ? personalDetails?.[cardA.accountID] ?? {} : {};
const userB = cardB.accountID ? personalDetails?.[cardB.accountID] ?? {} : {};

const aName = PersonalDetailsUtils.getDisplayNameOrDefault(userA);
const bName = PersonalDetailsUtils.getDisplayNameOrDefault(userB);
Expand Down Expand Up @@ -251,17 +251,15 @@ function isCustomFeed(feed: CompanyCardFeed): boolean {
return [CONST.COMPANY_CARD.FEED_BANK_NAME.MASTER_CARD, CONST.COMPANY_CARD.FEED_BANK_NAME.VISA, CONST.COMPANY_CARD.FEED_BANK_NAME.AMEX].some((value) => value === feed);
}

function getCompanyFeeds(cardFeeds: OnyxEntry<CardFeeds>): CompanyFeeds {
return {...cardFeeds?.settings?.companyCards, ...cardFeeds?.settings?.oAuthAccountDetails};
}

function removeExpensifyCardFromCompanyCards(cardFeeds: OnyxEntry<CardFeeds>): CompanyFeeds {
if (!cardFeeds) {
return {};
}

const companyCards = getCompanyFeeds(cardFeeds);
return Object.fromEntries(Object.entries(companyCards).filter(([key]) => key !== CONST.EXPENSIFY_CARD.BANK));
function getCompanyFeeds(cardFeeds: OnyxEntry<CardFeeds>, shouldFilterOutRemovedFeeds = false): CompanyFeeds {
return Object.fromEntries(
Object.entries(cardFeeds?.settings?.companyCards ?? {}).filter(([key, value]) => {
if (shouldFilterOutRemovedFeeds && value.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) {
return false;
}
return key !== CONST.EXPENSIFY_CARD.BANK;
}),
);
}

function getCardFeedName(feedType: CompanyCardFeed): string {
Expand Down Expand Up @@ -348,7 +346,7 @@ const getCorrectStepForSelectedBank = (selectedBank: ValueOf<typeof CONST.COMPAN
};

function getSelectedFeed(lastSelectedFeed: OnyxEntry<CompanyCardFeed>, cardFeeds: OnyxEntry<CardFeeds>): CompanyCardFeed | undefined {
const defaultFeed = Object.keys(removeExpensifyCardFromCompanyCards(cardFeeds)).at(0) as CompanyCardFeed | undefined;
const defaultFeed = Object.keys(getCompanyFeeds(cardFeeds, true)).at(0) as CompanyCardFeed | undefined;
return lastSelectedFeed ?? defaultFeed;
}

Expand Down Expand Up @@ -410,7 +408,6 @@ export {
getSelectedFeed,
getCorrectStepForSelectedBank,
getCustomOrFormattedFeedName,
removeExpensifyCardFromCompanyCards,
getFilteredCardList,
hasOnlyOneCardToAssign,
checkIfNewFeedConnected,
Expand Down
92 changes: 73 additions & 19 deletions src/libs/actions/CompanyCards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,6 @@ function setWorkspaceCompanyCardFeedName(policyID: string, workspaceAccountID: n

function setWorkspaceCompanyCardTransactionLiability(workspaceAccountID: number, policyID: string, bankName: CompanyCardFeed, liabilityType: string) {
const authToken = NetworkStore.getAuthToken();
const isCustomFeed = CardUtils.isCustomFeed(bankName);
const feedUpdates = {
[bankName]: {liabilityType},
};
Expand All @@ -135,7 +134,7 @@ function setWorkspaceCompanyCardTransactionLiability(workspaceAccountID: number,
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${workspaceAccountID}`,
value: {
settings: isCustomFeed ? {companyCards: feedUpdates} : {oAuthAccountDetails: feedUpdates},
settings: {companyCards: feedUpdates},
},
},
],
Expand All @@ -151,41 +150,97 @@ function setWorkspaceCompanyCardTransactionLiability(workspaceAccountID: number,
API.write(WRITE_COMMANDS.SET_COMPANY_CARD_TRANSACTION_LIABILITY, parameters, onyxData);
}

function deleteWorkspaceCompanyCardFeed(policyID: string, workspaceAccountID: number, bankName: CompanyCardFeed, feedToOpen?: CompanyCardFeed) {
function deleteWorkspaceCompanyCardFeed(policyID: string, workspaceAccountID: number, bankName: CompanyCardFeed, cardIDs: string[], feedToOpen?: CompanyCardFeed) {
const authToken = NetworkStore.getAuthToken();
const isCustomFeed = CardUtils.isCustomFeed(bankName);
const feedUpdates = {[bankName]: null};
const optimisticFeedUpdates = {[bankName]: {pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE}};
const successFeedUpdates = {[bankName]: null};
const failureFeedUpdates = {[bankName]: {pendingAction: null, errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage')}};
const optimisticCardUpdates = Object.fromEntries(cardIDs.map((cardID) => [cardID, {pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE}]));
const successAndFailureCardUpdates = Object.fromEntries(cardIDs.map((cardID) => [cardID, {pendingAction: null}]));

const optimisticData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${workspaceAccountID}`,
value: {
settings: {
...(isCustomFeed ? {companyCards: feedUpdates} : {oAuthAccountDetails: feedUpdates}),
companyCards: optimisticFeedUpdates,
},
},
},
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceAccountID}_${bankName}`,
value: optimisticCardUpdates,
},
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.CARD_LIST,
value: optimisticCardUpdates,
},
];

const successData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${workspaceAccountID}`,
value: {
settings: {
...(isCustomFeed ? {companyCards: successFeedUpdates} : {oAuthAccountDetails: successFeedUpdates, companyCards: successFeedUpdates}),
companyCardNicknames: {
[bankName]: null,
},
},
},
},
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceAccountID}_${bankName}`,
value: successAndFailureCardUpdates,
},
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.CARD_LIST,
value: successAndFailureCardUpdates,
},
];

if (feedToOpen) {
optimisticData.push({
const failureData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${policyID}`,
value: feedToOpen,
});
}
key: `${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${workspaceAccountID}`,
value: {
settings: {
companyCards: failureFeedUpdates,
},
},
},
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceAccountID}_${bankName}`,
value: successAndFailureCardUpdates,
},
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.CARD_LIST,
value: successAndFailureCardUpdates,
},
];

optimisticData.push({
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${policyID}`,
value: feedToOpen ?? null,
});

const parameters = {
authToken,
policyID,
bankName,
};

API.write(WRITE_COMMANDS.DELETE_COMPANY_CARD_FEED, parameters, {optimisticData});
API.write(WRITE_COMMANDS.DELETE_COMPANY_CARD_FEED, parameters, {optimisticData, successData, failureData});
}

function assignWorkspaceCompanyCard(policyID: string, data?: Partial<AssignCardData>) {
Expand All @@ -194,7 +249,7 @@ function assignWorkspaceCompanyCard(policyID: string, data?: Partial<AssignCardD
}
const {bankName = '', email = '', encryptedCardNumber = '', startDate = '', cardName = ''} = data;
const assigneeDetails = PersonalDetailsUtils.getPersonalDetailByEmail(email);
const optimisticCardAssignedReportAction = ReportUtils.buildOptimisticCardAssignedReportAction(assigneeDetails?.accountID ?? -1);
const optimisticCardAssignedReportAction = ReportUtils.buildOptimisticCardAssignedReportAction(assigneeDetails?.accountID ?? CONST.DEFAULT_NUMBER_ID);

const parameters: AssignCompanyCardParams = {
policyID,
Expand All @@ -206,7 +261,7 @@ function assignWorkspaceCompanyCard(policyID: string, data?: Partial<AssignCardD
reportActionID: optimisticCardAssignedReportAction.reportActionID,
};
const policy = PolicyUtils.getPolicy(policyID);
const policyExpenseChat = ReportUtils.getPolicyExpenseChat(policy?.ownerAccountID ?? -1, policyID);
const policyExpenseChat = ReportUtils.getPolicyExpenseChat(policy?.ownerAccountID ?? CONST.DEFAULT_NUMBER_ID, policyID);

const onyxData: OnyxData = {
optimisticData: [
Expand Down Expand Up @@ -242,9 +297,9 @@ function assignWorkspaceCompanyCard(policyID: string, data?: Partial<AssignCardD
API.write(WRITE_COMMANDS.ASSIGN_COMPANY_CARD, parameters, onyxData);
}

function unassignWorkspaceCompanyCard(workspaceAccountID: number, bankName: string, card?: Card) {
function unassignWorkspaceCompanyCard(workspaceAccountID: number, bankName: string, card: Card) {
const authToken = NetworkStore.getAuthToken();
const cardID = card?.cardID ?? '-1';
const cardID = card.cardID;

const onyxData: OnyxData = {
optimisticData: [
Expand Down Expand Up @@ -319,7 +374,6 @@ function unassignWorkspaceCompanyCard(workspaceAccountID: number, bankName: stri

function updateWorkspaceCompanyCard(workspaceAccountID: number, cardID: string, bankName: CompanyCardFeed) {
const authToken = NetworkStore.getAuthToken();
const isCustomFeed = CardUtils.isCustomFeed(bankName);
const optimisticFeedUpdates = {[bankName]: {errors: null}};
const failureFeedUpdates = {[bankName]: {errors: {error: CONST.COMPANY_CARDS.CONNECTION_ERROR}}};

Expand Down Expand Up @@ -358,7 +412,7 @@ function updateWorkspaceCompanyCard(workspaceAccountID: number, cardID: string,
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${workspaceAccountID}`,
value: {
settings: isCustomFeed ? {companyCards: optimisticFeedUpdates} : {oAuthAccountDetails: optimisticFeedUpdates},
settings: {companyCards: optimisticFeedUpdates},
},
},
];
Expand Down Expand Up @@ -425,7 +479,7 @@ function updateWorkspaceCompanyCard(workspaceAccountID: number, cardID: string,
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${workspaceAccountID}`,
value: {
settings: isCustomFeed ? {companyCards: failureFeedUpdates} : {oAuthAccountDetails: failureFeedUpdates},
settings: {companyCards: failureFeedUpdates},
},
},
];
Expand Down
4 changes: 2 additions & 2 deletions src/pages/workspace/WorkspaceMoreFeaturesPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro
!!policy?.connections?.xero?.config?.importTaxRates ||
!!policy?.connections?.netsuite?.options?.config?.syncOptions?.syncTax;
const policyID = policy?.id;
const workspaceAccountID = policy?.workspaceAccountID ?? -1;
const workspaceAccountID = policy?.workspaceAccountID ?? CONST.DEFAULT_NUMBER_ID;
const [cardsList] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceAccountID.toString()}_${CONST.EXPENSIFY_CARD.BANK}`);
const [cardFeeds] = useOnyx(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${workspaceAccountID.toString()}`);
const [isOrganizeWarningModalOpen, setIsOrganizeWarningModalOpen] = useState(false);
Expand Down Expand Up @@ -131,7 +131,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro
subtitleTranslationKey: 'workspace.moreFeatures.companyCards.subtitle',
isActive: policy?.areCompanyCardsEnabled ?? false,
pendingAction: policy?.pendingFields?.areCompanyCardsEnabled,
disabled: !isEmptyObject(CardUtils.removeExpensifyCardFromCompanyCards(cardFeeds)),
disabled: !isEmptyObject(CardUtils.getCompanyFeeds(cardFeeds)),
action: (isEnabled: boolean) => {
if (!policyID) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,15 @@ function WorkspaceCompanyCardDetailsPage({route}: WorkspaceCompanyCardDetailsPag
const card = allBankCards?.[cardID];

const cardBank = card?.bank ?? '';
const cardholder = personalDetails?.[card?.accountID ?? -1];
const cardholder = personalDetails?.[card?.accountID ?? CONST.DEFAULT_NUMBER_ID];
const displayName = PersonalDetailsUtils.getDisplayNameOrDefault(cardholder);
const exportMenuItem = getExportMenuItem(connectedIntegration, policyID, translate, policy, card);

const unassignCard = () => {
setIsUnassignModalVisible(false);
CompanyCards.unassignWorkspaceCompanyCard(workspaceAccountID, bank, card);
if (card) {
CompanyCards.unassignWorkspaceCompanyCard(workspaceAccountID, bank, card);
}
Navigation.goBack();
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,14 @@ function WorkspaceCompanyCardFeedSelectorPage({route}: WorkspaceCompanyCardFeedS
const [lastSelectedFeed] = useOnyx(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${policyID}`);
const selectedFeed = CardUtils.getSelectedFeed(lastSelectedFeed, cardFeeds);
const companyFeeds = CardUtils.getCompanyFeeds(cardFeeds);
const availableCards = CardUtils.removeExpensifyCardFromCompanyCards(cardFeeds);

const feeds: CardFeedListItem[] = (Object.keys(availableCards) as CompanyCardFeed[]).map((feed) => ({
const feeds: CardFeedListItem[] = (Object.keys(companyFeeds) as CompanyCardFeed[]).map((feed) => ({
value: feed,
text: CardUtils.getCustomOrFormattedFeedName(feed, cardFeeds?.settings?.companyCardNicknames),
keyForList: feed,
isSelected: feed === selectedFeed,
isDisabled: companyFeeds[feed]?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE,
pendingAction: companyFeeds[feed]?.pendingAction,
brickRoadIndicator: companyFeeds[feed]?.errors ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined,
canShowSeveralIndicators: !!companyFeeds[feed]?.errors,
leftElement: (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ function WorkspaceCompanyCardPage({route}: WorkspaceCompanyCardPageProps) {

const filteredCardList = CardUtils.getFilteredCardList(cardsList, selectedFeed ? cardFeeds?.settings?.oAuthAccountDetails?.[selectedFeed] : undefined);

const companyCards = CardUtils.removeExpensifyCardFromCompanyCards(cardFeeds);
const companyCards = CardUtils.getCompanyFeeds(cardFeeds);
const selectedFeedData = selectedFeed && companyCards[selectedFeed];
const isNoFeed = isEmptyObject(companyCards) && !selectedFeedData;
const isNoFeed = !selectedFeedData;
const isPending = !!selectedFeedData?.pending;
const isFeedAdded = !isPending && !isNoFeed;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,14 @@ function WorkspaceCompanyCardsSettingsPage({
const styles = useThemeStyles();
const {translate} = useLocalize();
const policy = usePolicy(policyID);
const workspaceAccountID = policy?.workspaceAccountID ?? -1;
const workspaceAccountID = policy?.workspaceAccountID ?? CONST.DEFAULT_NUMBER_ID;
const [deleteCompanyCardConfirmModalVisible, setDeleteCompanyCardConfirmModalVisible] = useState(false);

const [cardFeeds] = useOnyx(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${workspaceAccountID}`);
const [lastSelectedFeed] = useOnyx(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${policyID}`);
// eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps -- we want to run the hook only once to escape unexpected feed change
const selectedFeed = useMemo(() => CardUtils.getSelectedFeed(lastSelectedFeed, cardFeeds), []);
const [cardsList] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceAccountID}_${selectedFeed}`);
const feedName = CardUtils.getCustomOrFormattedFeedName(selectedFeed, cardFeeds?.settings?.companyCardNicknames);
const companyFeeds = CardUtils.getCompanyFeeds(cardFeeds);
const liabilityType = selectedFeed && companyFeeds[selectedFeed]?.liabilityType;
Expand All @@ -53,8 +54,12 @@ function WorkspaceCompanyCardsSettingsPage({

const deleteCompanyCardFeed = () => {
if (selectedFeed) {
const feedToOpen = (Object.keys(companyFeeds) as CompanyCardFeed[]).filter((feed) => feed !== selectedFeed).at(0);
CompanyCards.deleteWorkspaceCompanyCardFeed(policyID, workspaceAccountID, selectedFeed, feedToOpen);
const {cardList, ...cards} = cardsList ?? {};
const cardIDs = Object.keys(cards);
const feedToOpen = (Object.keys(companyFeeds) as CompanyCardFeed[])
.filter((feed) => feed !== selectedFeed && companyFeeds[feed]?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE)
.at(0);
CompanyCards.deleteWorkspaceCompanyCardFeed(policyID, workspaceAccountID, selectedFeed, cardIDs, feedToOpen);
}
setDeleteCompanyCardConfirmModalVisible(false);
Navigation.setNavigationActionToMicrotaskQueue(Navigation.goBack);
Expand Down
4 changes: 2 additions & 2 deletions src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,10 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM
const isSelectedMemberCurrentUser = accountID === currentUserPersonalDetails?.accountID;
const isCurrentUserAdmin = policy?.employeeList?.[personalDetails?.[currentUserPersonalDetails?.accountID]?.login ?? '']?.role === CONST.POLICY.ROLE.ADMIN;
const isCurrentUserOwner = policy?.owner === currentUserPersonalDetails?.login;
const ownerDetails = personalDetails?.[policy?.ownerAccountID ?? -1] ?? ({} as PersonalDetails);
const ownerDetails = personalDetails?.[policy?.ownerAccountID ?? CONST.DEFAULT_NUMBER_ID] ?? ({} as PersonalDetails);
const policyOwnerDisplayName = formatPhoneNumber(PersonalDetailsUtils.getDisplayNameOrDefault(ownerDetails)) ?? policy?.owner ?? '';
const hasMultipleFeeds = Object.values(CardUtils.getCompanyFeeds(cardFeeds)).filter((feed) => !feed.pending).length > 0;
const paymentAccountID = cardSettings?.paymentBankAccountID ?? 0;
const paymentAccountID = cardSettings?.paymentBankAccountID ?? CONST.DEFAULT_NUMBER_ID;

useEffect(() => {
CompanyCards.openPolicyCompanyCardsPage(policyID, workspaceAccountID);
Expand Down
Loading

0 comments on commit cdefab1

Please sign in to comment.