Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3278,6 +3278,7 @@ const CONST = {
BANK_CONNECTION: 'BankConnection',
PLAID_CONNECTION: 'PlaidConnection',
ASSIGNEE: 'Assignee',
INVITE_NEW_MEMBER: 'InviteNewMember',
CARD: 'Card',
CARD_NAME: 'CardName',
TRANSACTION_START_DATE: 'TransactionStartDate',
Expand Down Expand Up @@ -3320,6 +3321,7 @@ const CONST = {
STEP_NAMES: ['1', '2', '3', '4', '5', '6'],
STEP: {
ASSIGNEE: 'Assignee',
INVITE_NEW_MEMBER: 'InviteNewMember',
CARD_TYPE: 'CardType',
LIMIT_TYPE: 'LimitType',
LIMIT: 'Limit',
Expand Down
2 changes: 1 addition & 1 deletion src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -994,7 +994,7 @@ const ROUTES = {
},
WORKSPACE_INVITE_MESSAGE_ROLE: {
route: 'settings/workspaces/:policyID/invite-message/role',
getRoute: (policyID: string, backTo?: string) => `${getUrlWithBackToParam(`settings/workspaces/${policyID}/invite-message/role`, backTo)}` as const,
getRoute: (policyID: string | undefined, backTo?: string) => `${getUrlWithBackToParam(`settings/workspaces/${policyID}/invite-message/role`, backTo)}` as const,
},
WORKSPACE_OVERVIEW: {
route: 'settings/workspaces/:policyID/overview',
Expand Down
1 change: 1 addition & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4826,6 +4826,7 @@ const translations = {
invite: {
member: 'Invite member',
members: 'Invite members',
inviteNewMember: 'Invite new member',
invitePeople: 'Invite new members',
genericFailureMessage: 'An error occurred while inviting the member to the workspace. Please try again.',
pleaseEnterValidLogin: `Please ensure the email or phone number is valid (e.g. ${CONST.EXAMPLE_PHONE_NUMBER}).`,
Expand Down
1 change: 1 addition & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4873,6 +4873,7 @@ const translations = {
invite: {
member: 'Invitar miembros',
members: 'Invitar miembros',
inviteNewMember: 'Invitar nuevo miembro',
invitePeople: 'Invitar nuevos miembros',
genericFailureMessage: 'Se ha producido un error al invitar al miembro al espacio de trabajo. Vuelva a intentarlo.',
pleaseEnterValidLogin: `Asegúrese de que el correo electrónico o el número de teléfono sean válidos (p. ej. ${CONST.EXAMPLE_PHONE_NUMBER}).`,
Expand Down
9 changes: 7 additions & 2 deletions src/libs/actions/Policy/Member.ts
Original file line number Diff line number Diff line change
Expand Up @@ -967,7 +967,12 @@ function buildAddMembersToWorkspaceOnyxData(invitedEmailsToAccountIDs: InvitedEm
* Adds members to the specified workspace/policyID
* Please see https://github.com/Expensify/App/blob/main/README.md#Security for more details
*/
function addMembersToWorkspace(invitedEmailsToAccountIDs: InvitedEmailsToAccountIDs, welcomeNote: string, policyID: string, policyMemberAccountIDs: number[], role: string) {
function addMembersToWorkspace(invitedEmailsToAccountIDs: InvitedEmailsToAccountIDs, welcomeNote: string, policyID: string | undefined, policyMemberAccountIDs: number[], role: string) {
if (!policyID) {
Log.warn('addMembersToWorkspace missing policyID', {invitedEmailsToAccountIDs, welcomeNote, policyMemberAccountIDs, role});
return;
}

const {optimisticData, successData, failureData, optimisticAnnounceChat, membersChats, logins} = buildAddMembersToWorkspaceOnyxData(
invitedEmailsToAccountIDs,
policyID,
Expand Down Expand Up @@ -1165,7 +1170,7 @@ function setWorkspaceInviteRoleDraft(policyID: string, role: ValueOf<typeof CONS
Onyx.set(`${ONYXKEYS.COLLECTION.WORKSPACE_INVITE_ROLE_DRAFT}${policyID}`, role);
}

function clearWorkspaceInviteRoleDraft(policyID: string) {
function clearWorkspaceInviteRoleDraft(policyID: string | undefined) {
Onyx.set(`${ONYXKEYS.COLLECTION.WORKSPACE_INVITE_ROLE_DRAFT}${policyID}`, null);
}

Expand Down
2 changes: 1 addition & 1 deletion src/libs/actions/Policy/Policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2526,7 +2526,7 @@ function updateMemberCustomField(policyID: string, login: string, customFieldTyp
API.write(WRITE_COMMANDS.UPDATE_POLICY_MEMBERS_CUSTOM_FIELDS, params, {optimisticData, successData, failureData});
}

function setWorkspaceInviteMessageDraft(policyID: string, message: string | null) {
function setWorkspaceInviteMessageDraft(policyID: string | undefined, message: string | null) {
Onyx.set(`${ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MESSAGE_DRAFT}${policyID}`, message);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import AssigneeStep from './AssigneeStep';
import CardNameStep from './CardNameStep';
import CardSelectionStep from './CardSelectionStep';
import ConfirmationStep from './ConfirmationStep';
import InviteNewMemberStep from './InviteNewMemberStep';
import TransactionStartDateStep from './TransactionStartDateStep';

type AssignCardFeedPageProps = PlatformStackScreenProps<SettingsNavigatorParamList, typeof SCREENS.WORKSPACE.COMPANY_CARDS_ASSIGN_CARD> & WithPolicyAndFullscreenLoadingProps;
Expand Down Expand Up @@ -68,6 +69,13 @@ function AssignCardFeedPage({route, policy}: AssignCardFeedPageProps) {
feed={feed}
/>
);
case CONST.COMPANY_CARD.STEP.INVITE_NEW_MEMBER:
return (
<InviteNewMemberStep
policy={policy}
feed={feed}
/>
);
case CONST.COMPANY_CARD.STEP.CARD:
return (
<CardSelectionStep
Expand Down
110 changes: 96 additions & 14 deletions src/pages/workspace/companyCards/assignCard/AssigneeStep.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import React, {useMemo, useState} from 'react';
import React, {useEffect, useMemo, useState} from 'react';
import {Keyboard} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import {useOnyx} from 'react-native-onyx';
import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton';
import * as Expensicons from '@components/Icon/Expensicons';
import InteractiveStepWrapper from '@components/InteractiveStepWrapper';
import {useBetas} from '@components/OnyxProvider';
import {useOptionsList} from '@components/OptionListContextProvider';
import SelectionList from '@components/SelectionList';
import type {ListItem} from '@components/SelectionList/types';
import UserListItem from '@components/SelectionList/UserListItem';
Expand All @@ -15,9 +17,10 @@ import useDebouncedState from '@hooks/useDebouncedState';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useThemeStyles from '@hooks/useThemeStyles';
import {searchInServer} from '@libs/actions/Report';
import {getDefaultCardName, getFilteredCardList, hasOnlyOneCardToAssign} from '@libs/CardUtils';
import {formatPhoneNumber} from '@libs/LocalePhoneNumber';
import {getHeaderMessage, getSearchValueForPhoneOrEmail, sortAlphabetically} from '@libs/OptionsListUtils';
import {filterAndOrderOptions, getHeaderMessage, getValidOptions, sortAlphabetically} from '@libs/OptionsListUtils';
import {getPersonalDetailByEmail} from '@libs/PersonalDetailsUtils';
import {isDeletedPolicyEmployee} from '@libs/PolicyUtils';
import tokenizedSearch from '@libs/tokenizedSearch';
Expand All @@ -38,6 +41,60 @@ type AssigneeStepProps = {
feed: OnyxTypes.CompanyCardFeed;
};

function useOptions() {
const betas = useBetas();
const [isLoading, setIsLoading] = useState(true);
const [searchValue, debouncedSearchValue, setSearchValue] = useDebouncedState('');
const {options: optionsList, areOptionsInitialized} = useOptionsList();

const defaultOptions = useMemo(() => {
const {recentReports, personalDetails, userToInvite, currentUserOption} = getValidOptions(
{
reports: optionsList.reports,
personalDetails: optionsList.personalDetails,
},
{
betas,
excludeLogins: {...CONST.EXPENSIFY_EMAILS_OBJECT},
},
);

const headerMessage = getHeaderMessage((recentReports?.length || 0) + (personalDetails?.length || 0) !== 0, !!userToInvite, '');

if (isLoading) {
// eslint-disable-next-line react-compiler/react-compiler
setIsLoading(false);
}

return {
userToInvite,
recentReports,
personalDetails,
currentUserOption,
headerMessage,
};
}, [optionsList.reports, optionsList.personalDetails, betas, isLoading]);

const options = useMemo(() => {
const filteredOptions = filterAndOrderOptions(defaultOptions, debouncedSearchValue.trim(), {
excludeLogins: {...CONST.EXPENSIFY_EMAILS_OBJECT},
maxRecentReportsToShow: CONST.IOU.MAX_RECENT_REPORTS_TO_SHOW,
});
const headerMessage = getHeaderMessage(
(filteredOptions.recentReports?.length || 0) + (filteredOptions.personalDetails?.length || 0) !== 0,
!!filteredOptions.userToInvite,
debouncedSearchValue,
);

return {
...filteredOptions,
headerMessage,
};
}, [debouncedSearchValue, defaultOptions]);

return {...options, searchValue, debouncedSearchValue, setSearchValue, areOptionsInitialized};
}

function AssigneeStep({policy, feed}: AssigneeStepProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();
Expand All @@ -50,13 +107,23 @@ function AssigneeStep({policy, feed}: AssigneeStepProps) {
const isEditing = assignCard?.isEditing;

const [selectedMember, setSelectedMember] = useState(assignCard?.data?.email ?? '');
const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState('');
const {userToInvite, personalDetails, searchValue, debouncedSearchValue, setSearchValue, areOptionsInitialized, headerMessage} = useOptions();
const [shouldShowError, setShouldShowError] = useState(false);

const selectMember = (assignee: ListItem) => {
Keyboard.dismiss();
setSelectedMember(assignee.login ?? '');
setShouldShowError(false);

if (userToInvite?.login === assignee.login) {
setAssignCardStepAndData({
currentStep: CONST.COMPANY_CARD.STEP.INVITE_NEW_MEMBER,
data: {
email: assignee?.login,
assigneeAccountID: assignee?.accountID ?? CONST.DEFAULT_NUMBER_ID,
},
});
}
};

const submit = () => {
Expand Down Expand Up @@ -144,7 +211,7 @@ function AssigneeStep({policy, feed}: AssigneeStepProps) {
}, [isOffline, policy?.employeeList, selectedMember]);

const sections = useMemo(() => {
if (!debouncedSearchTerm) {
if (!debouncedSearchValue) {
return [
{
data: membersDetails,
Expand All @@ -153,7 +220,6 @@ function AssigneeStep({policy, feed}: AssigneeStepProps) {
];
}

const searchValue = getSearchValueForPhoneOrEmail(debouncedSearchTerm).toLowerCase();
const filteredOptions = tokenizedSearch(membersDetails, searchValue, (option) => [option.text ?? '', option.alternateText ?? '']);

return [
Expand All @@ -162,14 +228,30 @@ function AssigneeStep({policy, feed}: AssigneeStepProps) {
data: filteredOptions,
shouldShow: true,
},
...(userToInvite
? [
{
title: undefined,
data: [userToInvite],
shouldShow: true,
},
]
: []),
...(personalDetails?.length > 0
? [
{
title: undefined,
data: personalDetails,
shouldShow: true,
},
]
: []),
];
}, [membersDetails, debouncedSearchTerm]);

const headerMessage = useMemo(() => {
const searchValue = debouncedSearchTerm.trim().toLowerCase();
}, [debouncedSearchValue, membersDetails, personalDetails, searchValue, userToInvite]);

return getHeaderMessage(sections[0].data.length !== 0, false, searchValue);
}, [debouncedSearchTerm, sections]);
useEffect(() => {
searchInServer(debouncedSearchValue);
}, [debouncedSearchValue]);

return (
<InteractiveStepWrapper
Expand All @@ -183,9 +265,9 @@ function AssigneeStep({policy, feed}: AssigneeStepProps) {
<Text style={[styles.textHeadlineLineHeightXXL, styles.ph5, styles.mv3]}>{translate('workspace.companyCards.whoNeedsCardAssigned')}</Text>
<SelectionList
textInputLabel={textInputLabel}
textInputValue={searchTerm}
onChangeText={setSearchTerm}
sections={sections}
textInputValue={searchValue}
onChangeText={setSearchValue}
sections={areOptionsInitialized ? sections : []}
headerMessage={headerMessage}
ListItem={UserListItem}
onSelectRow={selectMember}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ function ConfirmationStep({policyID, backTo}: ConfirmationStepProps) {
const [cardFeeds] = useCardFeeds(policyID);

const data = assignCard?.data;
const cardholderName = getPersonalDetailByEmail(data?.email ?? '')?.displayName ?? '';
const cardholderName = getPersonalDetailByEmail(data?.email ?? '')?.displayName ?? data?.email;

useEffect(() => {
if (!assignCard?.isAssigned) {
Expand Down
Loading
Loading