diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 2a7e41df0b04..2451c5b4d21f 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -737,6 +737,12 @@ const ONYXKEYS = { /** Stores domain admin account ID */ EXPENSIFY_ADMIN_ACCESS_PREFIX: 'expensify_adminPermissions_', + + /** Pending actions for a domain */ + DOMAIN_PENDING_ACTIONS: 'domainPendingActions_', + + /** Errors related to a domain */ + DOMAIN_ERRORS: 'domainErrors_', }, /** List of Form ids */ @@ -1124,6 +1130,8 @@ type OnyxCollectionValuesMapping = { [ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_ADMIN_ACCESS]: boolean; [ONYXKEYS.COLLECTION.SAML_METADATA]: OnyxTypes.SamlMetadata; [ONYXKEYS.COLLECTION.EXPENSIFY_ADMIN_ACCESS_PREFIX]: number; + [ONYXKEYS.COLLECTION.DOMAIN_PENDING_ACTIONS]: OnyxTypes.DomainPendingActions; + [ONYXKEYS.COLLECTION.DOMAIN_ERRORS]: OnyxTypes.DomainErrors; }; type OnyxValuesMapping = { diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 0added7d4915..4bd2613fc54b 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -3456,6 +3456,14 @@ const ROUTES = { route: 'domain/:domainAccountID/admins/:accountID', getRoute: (domainAccountID: number, accountID: number) => `domain/${domainAccountID}/admins/${accountID}` as const, }, + DOMAIN_ADMINS_SETTINGS: { + route: 'domain/:domainAccountID/admins/settings', + getRoute: (domainAccountID: number) => `domain/${domainAccountID}/admins/settings` as const, + }, + DOMAIN_ADD_PRIMARY_CONTACT: { + route: 'domain/:domainAccountID/admins/settings/primary-contact', + getRoute: (domainAccountID: number) => `domain/${domainAccountID}/admins/settings/primary-contact` as const, + }, } as const; /** diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 14ed40d2431c..35e57fd73698 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -854,6 +854,8 @@ const SCREENS = { SAML: 'Domain_SAML', ADMINS: 'Domain_Admins', ADMIN_DETAILS: 'Domain_Admin_Details', + ADMINS_SETTINGS: 'Admins_Settings', + ADD_PRIMARY_CONTACT: 'Add_Primary_Contact', }, } as const; diff --git a/src/languages/de.ts b/src/languages/de.ts index 094779da5c67..eb9fc8b8e94a 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -7968,7 +7968,7 @@ Hier ist ein *Testbeleg*, um dir zu zeigen, wie es funktioniert:`, subtitle: 'Erzwingen Sie für Mitglieder Ihrer Domain die Anmeldung per Single Sign-On, schränken Sie die Erstellung von Workspaces ein und vieles mehr.', enable: 'Aktivieren', }, - admins: {title: 'Admins', findAdmin: 'Admin finden'}, + admins: {title: 'Admins', findAdmin: 'Admin finden', primaryContact: 'Hauptansprechpartner', addPrimaryContact: 'Primären Kontakt hinzufügen', settings: 'Einstellungen'}, }, }; // IMPORTANT: This line is manually replaced in generate translation files by scripts/generateTranslations.ts, diff --git a/src/languages/en.ts b/src/languages/en.ts index 03fe8b9c961a..c8b67698fa06 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -7817,6 +7817,9 @@ const translations = { admins: { title: 'Admins', findAdmin: 'Find admin', + primaryContact: 'Primary contact', + addPrimaryContact: 'Add primary contact', + settings: 'Settings', }, }, }; diff --git a/src/languages/es.ts b/src/languages/es.ts index 52c73efba25e..b566eac04c3a 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -7964,6 +7964,9 @@ ${amount} para ${merchant} - ${date}`, admins: { title: 'Administradores', findAdmin: 'Encontrar administrador', + primaryContact: 'Contacto principal', + addPrimaryContact: 'Añadir contacto principal', + settings: 'Configuración', }, }, }; diff --git a/src/languages/fr.ts b/src/languages/fr.ts index 96296b391b80..4c9dd813e513 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -7973,7 +7973,7 @@ Voici un *reçu test* pour vous montrer comment cela fonctionne :`, subtitle: "Exiger que les membres de votre domaine se connectent via l'authentification unique, restreindre la création d'espaces de travail, et plus encore.", enable: 'Activer', }, - admins: {title: 'Admins', findAdmin: 'Trouver un admin'}, + admins: {title: 'Admins', findAdmin: 'Trouver un admin', primaryContact: 'Contact principal', addPrimaryContact: 'Ajouter un contact principal', settings: 'Paramètres'}, }, }; // IMPORTANT: This line is manually replaced in generate translation files by scripts/generateTranslations.ts, diff --git a/src/languages/it.ts b/src/languages/it.ts index 477a80f18091..18a2730a437d 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -7949,7 +7949,13 @@ Ecco una *ricevuta di prova* per mostrarti come funziona:`, subtitle: 'Richiedi ai membri del tuo dominio di accedere tramite Single Sign-On, limita la creazione di spazi di lavoro e altro ancora.', enable: 'Abilita', }, - admins: {title: 'Amministratori', findAdmin: 'Trova amministratore'}, + admins: { + title: 'Amministratori', + findAdmin: 'Trova amministratore', + primaryContact: 'Contatto principale', + addPrimaryContact: 'Aggiungi contatto principale', + settings: 'Impostazioni', + }, }, }; // IMPORTANT: This line is manually replaced in generate translation files by scripts/generateTranslations.ts, diff --git a/src/languages/ja.ts b/src/languages/ja.ts index 629c5c140935..1cf7c086a0dd 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -7888,7 +7888,7 @@ Expensify の使い方をお見せするための*テストレシート*がこ subtitle: 'ドメインのメンバーにシングルサインオンでのログインを必須化し、ワークスペースの作成を制限するなど、さらに多くのことができます。', enable: '有効にする', }, - admins: {title: '管理者', findAdmin: '管理者を検索'}, + admins: {title: '管理者', findAdmin: '管理者を検索', primaryContact: '主要連絡先', addPrimaryContact: '主要連絡先を追加', settings: '設定'}, }, }; // IMPORTANT: This line is manually replaced in generate translation files by scripts/generateTranslations.ts, diff --git a/src/languages/nl.ts b/src/languages/nl.ts index 9d421cb3df38..24d54e8e727e 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -7935,7 +7935,13 @@ Hier is een *testbon* om je te laten zien hoe het werkt:`, subtitle: 'Verplicht leden van je domein om in te loggen via single sign-on, beperk het aanmaken van werkruimten en meer.', enable: 'Inschakelen', }, - admins: {title: 'Beheerders', findAdmin: 'Beheerder zoeken'}, + admins: { + title: 'Beheerders', + findAdmin: 'Beheerder zoeken', + primaryContact: 'Primair contactpersoon', + addPrimaryContact: 'Primair contactpersoon toevoegen', + settings: 'Instellingen', + }, }, }; // IMPORTANT: This line is manually replaced in generate translation files by scripts/generateTranslations.ts, diff --git a/src/languages/pl.ts b/src/languages/pl.ts index b96a188529bb..9792d225428b 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -7920,7 +7920,7 @@ Oto *paragon testowy*, który pokazuje, jak to działa:`, subtitle: 'Wymagaj, aby członkowie Twojej domeny logowali się przez Single Sign-On (SSO), ograniczaj tworzenie obszarów roboczych i nie tylko.', enable: 'Włącz', }, - admins: {title: 'Administratorzy', findAdmin: 'Znajdź administratora'}, + admins: {title: 'Administratorzy', findAdmin: 'Znajdź administratora', primaryContact: 'Główny kontakt', addPrimaryContact: 'Dodaj główny kontakt', settings: 'Ustawienia'}, }, }; // IMPORTANT: This line is manually replaced in generate translation files by scripts/generateTranslations.ts, diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index 278569275647..612f5c003cbb 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -7927,7 +7927,13 @@ Aqui está um *recibo de teste* para mostrar como funciona:`, subtitle: 'Exija que os membros do seu domínio façam login por meio de logon único (SSO), restrinja a criação de espaços de trabalho e muito mais.', enable: 'Ativar', }, - admins: {title: 'Administradores', findAdmin: 'Encontrar administrador'}, + admins: { + title: 'Administradores', + findAdmin: 'Encontrar administrador', + primaryContact: 'Contato principal', + addPrimaryContact: 'Adicionar contato principal', + settings: 'Configurações', + }, }, }; // IMPORTANT: This line is manually replaced in generate translation files by scripts/generateTranslations.ts, diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index cde1f8c2f80e..3421de473bdd 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -7753,7 +7753,7 @@ ${reportName} addDomain: {title: '添加域', subtitle: '请输入您想访问的私有域名(例如:expensify.com)。', domainName: '域名', newDomain: '新域名'}, domainAdded: {title: '已添加域名', description: '接下来,您需要验证域名的所有权并调整您的安全设置。', configure: '配置'}, enhancedSecurity: {title: '增强的安全性', subtitle: '要求您域内的成员使用单点登录登录、限制工作区创建等。', enable: '启用'}, - admins: {title: '管理员', findAdmin: '查找管理员'}, + admins: {title: '管理员', findAdmin: '查找管理员', primaryContact: '主要联系人', addPrimaryContact: '添加主要联系人', settings: '设置'}, }, }; // IMPORTANT: This line is manually replaced in generate translation files by scripts/generateTranslations.ts, diff --git a/src/libs/API/parameters/SetTechnicalContactEmailParams.ts b/src/libs/API/parameters/SetTechnicalContactEmailParams.ts new file mode 100644 index 000000000000..d345d97c6ee6 --- /dev/null +++ b/src/libs/API/parameters/SetTechnicalContactEmailParams.ts @@ -0,0 +1,7 @@ +type SetTechnicalContactEmailParams = { + authToken?: string | null; + domainAccountID: number; + technicalContactAccountID: number; +}; + +export default SetTechnicalContactEmailParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 45c0691ea8d3..23af9c22e263 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -448,3 +448,4 @@ export type {default as SetSamlIdentityParams} from './SetSamlIdentityParams'; export type {default as UpdateSamlEnabledParams} from './UpdateSamlEnabledParams'; export type {default as UpdateSamlRequiredParams} from './UpdateSamlRequiredParams'; export type {default as SetPolicyRequireCompanyCardsEnabledParams} from './SetPolicyRequireCompanyCardsEnabled'; +export type {default as SetTechnicalContactEmailParams} from './SetTechnicalContactEmailParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index d299b4f5d317..694a9008b6df 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -526,6 +526,7 @@ const WRITE_COMMANDS = { UPDATE_SAML_ENABLED: 'UpdateSAMLEnabled', UPDATE_SAML_REQUIRED: 'UpdateSAMLRequired', CREATE_DOMAIN: 'CreateDomain', + SET_TECHNICAL_CONTACT_EMAIL: 'SetTechnicalContactEmail', } as const; type WriteCommand = ValueOf; @@ -1073,6 +1074,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.UPDATE_SAML_ENABLED]: Parameters.UpdateSamlEnabledParams; [WRITE_COMMANDS.UPDATE_SAML_REQUIRED]: Parameters.UpdateSamlRequiredParams; [WRITE_COMMANDS.CREATE_DOMAIN]: Parameters.DomainParams; + [WRITE_COMMANDS.SET_TECHNICAL_CONTACT_EMAIL]: Parameters.SetTechnicalContactEmailParams; }; const READ_COMMANDS = { diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index e43e06fcfa88..ade0b5aa672b 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -848,6 +848,8 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/domain/SamlVerifyDomainPage').default, [SCREENS.DOMAIN.VERIFIED]: () => require('../../../../pages/domain/SamlDomainVerifiedPage').default, [SCREENS.DOMAIN.ADMIN_DETAILS]: () => require('../../../../pages/domain/Admins/DomainAdminDetailsPage').default, + [SCREENS.DOMAIN.ADMINS_SETTINGS]: () => require('../../../../pages/domain/Admins/DomainAdminsSettingsPage').default, + [SCREENS.DOMAIN.ADD_PRIMARY_CONTACT]: () => require('../../../../pages/domain/Admins/DomainAddPrimaryContactPage').default, }); const TwoFactorAuthenticatorStackNavigator = createModalStackNavigator({ diff --git a/src/libs/Navigation/linkingConfig/RELATIONS/DOMAIN_TO_RHP.ts b/src/libs/Navigation/linkingConfig/RELATIONS/DOMAIN_TO_RHP.ts index c7769a21a367..ea0c54caf54d 100755 --- a/src/libs/Navigation/linkingConfig/RELATIONS/DOMAIN_TO_RHP.ts +++ b/src/libs/Navigation/linkingConfig/RELATIONS/DOMAIN_TO_RHP.ts @@ -5,7 +5,7 @@ import SCREENS from '@src/SCREENS'; const DOMAIN_TO_RHP: Partial> = { [SCREENS.DOMAIN.INITIAL]: [], [SCREENS.DOMAIN.SAML]: [SCREENS.DOMAIN.VERIFY, SCREENS.DOMAIN.VERIFIED], - [SCREENS.DOMAIN.ADMINS]: [SCREENS.DOMAIN.ADMIN_DETAILS], + [SCREENS.DOMAIN.ADMINS]: [SCREENS.DOMAIN.ADMIN_DETAILS, SCREENS.DOMAIN.ADMINS_SETTINGS, SCREENS.DOMAIN.ADD_PRIMARY_CONTACT], }; export default DOMAIN_TO_RHP; diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index c56cbe08a498..a1afb0029769 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -1111,6 +1111,12 @@ const config: LinkingOptions['config'] = { [SCREENS.DOMAIN.ADMIN_DETAILS]: { path: ROUTES.DOMAIN_ADMIN_DETAILS.route, }, + [SCREENS.DOMAIN.ADMINS_SETTINGS]: { + path: ROUTES.DOMAIN_ADMINS_SETTINGS.route, + }, + [SCREENS.DOMAIN.ADD_PRIMARY_CONTACT]: { + path: ROUTES.DOMAIN_ADD_PRIMARY_CONTACT.route, + }, }, }, [SCREENS.RIGHT_MODAL.TWO_FACTOR_AUTH]: { diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 7f905e2b90bf..0b6298157098 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -1332,6 +1332,12 @@ type SettingsNavigatorParamList = { domainAccountID: number; accountID: number; }; + [SCREENS.DOMAIN.ADMINS_SETTINGS]: { + domainAccountID: number; + }; + [SCREENS.DOMAIN.ADD_PRIMARY_CONTACT]: { + domainAccountID: number; + }; } & ReimbursementAccountNavigatorParamList; type DomainCardNavigatorParamList = { diff --git a/src/libs/actions/Domain.ts b/src/libs/actions/Domain.ts index 4c2d982dd090..51d553f1d841 100644 --- a/src/libs/actions/Domain.ts +++ b/src/libs/actions/Domain.ts @@ -1,8 +1,10 @@ import Onyx from 'react-native-onyx'; import type {OnyxUpdate} from 'react-native-onyx'; import * as API from '@libs/API'; +import type {SetTechnicalContactEmailParams} from '@libs/API/parameters'; import {READ_COMMANDS, SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; import {getMicroSecondOnyxErrorWithTranslationKey} from '@libs/ErrorUtils'; +import {getAuthToken} from '@libs/Network/NetworkStore'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {ScimTokenWithState} from './ScimToken/ScimTokenUtils'; @@ -351,6 +353,69 @@ function resetCreateDomainForm() { Onyx.merge(ONYXKEYS.FORMS.CREATE_DOMAIN_FORM, null); } +function setPrimaryContact(domainAccountID: number, newTechnicalContactAccountID: number, newTechnicalContactEmail: string, currentTechnicalContactEmail?: string) { + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${domainAccountID}`, + value: { + settings: { + technicalContactEmail: newTechnicalContactEmail, + }, + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.DOMAIN_PENDING_ACTIONS}${domainAccountID}`, + value: { + technicalContactEmail: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + }, + }, + ]; + const successData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.DOMAIN_PENDING_ACTIONS}${domainAccountID}`, + value: { + technicalContactEmail: null, + }, + }, + ]; + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${domainAccountID}`, + value: { + settings: { + technicalContactEmail: currentTechnicalContactEmail, + }, + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.DOMAIN_PENDING_ACTIONS}${domainAccountID}`, + value: { + technicalContactEmail: null, + }, + }, + ]; + + const authToken = getAuthToken(); + const params: SetTechnicalContactEmailParams = { + authToken, + domainAccountID, + technicalContactAccountID: newTechnicalContactAccountID, + }; + + API.write(WRITE_COMMANDS.SET_TECHNICAL_CONTACT_EMAIL, params, {optimisticData, successData, failureData}); +} + +function clearSetPrimaryContactError(domainAccountID: number) { + Onyx.merge(`${ONYXKEYS.COLLECTION.DOMAIN_ERRORS}${domainAccountID}`, { + technicalContactEmailErrors: null, + }); +} + export { getDomainValidationCode, validateDomain, @@ -365,4 +430,6 @@ export { getScimToken, createDomain, resetCreateDomainForm, + setPrimaryContact, + clearSetPrimaryContactError, }; diff --git a/src/pages/domain/Admins/DomainAddPrimaryContactPage.tsx b/src/pages/domain/Admins/DomainAddPrimaryContactPage.tsx new file mode 100644 index 000000000000..9aa5a0c7506a --- /dev/null +++ b/src/pages/domain/Admins/DomainAddPrimaryContactPage.tsx @@ -0,0 +1,133 @@ +import {adminAccountIDsSelector, technicalContactEmailSelector} from '@selectors/Domain'; +import React from 'react'; +import type {OnyxEntry} from 'react-native-onyx'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import SelectionList from '@components/SelectionList'; +import InviteMemberListItem from '@components/SelectionList/ListItem/InviteMemberListItem'; +import type {ListItem} from '@components/SelectionList/types'; +import useDebouncedState from '@hooks/useDebouncedState'; +import useLocalize from '@hooks/useLocalize'; +import useOnyx from '@hooks/useOnyx'; +import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; +import {getSearchValueForPhoneOrEmail, sortAlphabetically} from '@libs/OptionsListUtils'; +import {getDisplayNameOrDefault} from '@libs/PersonalDetailsUtils'; +import tokenizedSearch from '@libs/tokenizedSearch'; +import Navigation from '@navigation/Navigation'; +import type {SettingsNavigatorParamList} from '@navigation/types'; +import DomainNotFoundPageWrapper from '@pages/domain/DomainNotFoundPageWrapper'; +import {setPrimaryContact} from '@userActions/Domain'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; +import type {PersonalDetailsList} from '@src/types/onyx'; + +type AdminOption = Omit & { + accountID: number; + login: string; +}; +type DomainAddPrimaryContactPageProps = PlatformStackScreenProps; + +function DomainAddPrimaryContactPage({route}: DomainAddPrimaryContactPageProps) { + const {domainAccountID} = route.params; + const {translate, formatPhoneNumber, localeCompare} = useLocalize(); + const [adminAccountIDs] = useOnyx(`${ONYXKEYS.COLLECTION.DOMAIN}${domainAccountID}`, { + canBeMissing: true, + selector: adminAccountIDsSelector, + }); + // eslint-disable-next-line rulesdir/no-inline-useOnyx-selector + const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, { + canBeMissing: true, + selector: (personalDetailsList: OnyxEntry) => { + if (!personalDetailsList) { + return undefined; + } + + const adminsPersonalDetails: OnyxEntry = {}; + for (const accountID of adminAccountIDs ?? []) { + adminsPersonalDetails[`${accountID}`] = personalDetailsList[accountID]; + } + + return adminsPersonalDetails; + }, + }); + const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(''); + const [countryCode = CONST.DEFAULT_COUNTRY_CODE] = useOnyx(ONYXKEYS.COUNTRY_CODE, {canBeMissing: false}); + const [technicalContactEmail] = useOnyx(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${domainAccountID}`, { + canBeMissing: false, + selector: technicalContactEmailSelector, + }); + + let technicalContactEmailKey: string | undefined; + const data: AdminOption[] = []; + for (const accountID of adminAccountIDs ?? []) { + const details = personalDetails?.[accountID]; + if (details?.login === technicalContactEmail) { + technicalContactEmailKey = String(accountID); + } + data.push({ + isSelected: details?.login === technicalContactEmail, + keyForList: String(accountID), + accountID, + login: details?.login ?? '', + text: formatPhoneNumber(getDisplayNameOrDefault(details)), + alternateText: formatPhoneNumber(details?.login ?? ''), + icons: [ + { + source: details?.avatar ?? details?.fallbackIcon ?? '', + name: formatPhoneNumber(details?.login ?? ''), + type: CONST.ICON_TYPE_AVATAR, + id: accountID, + }, + ], + }); + } + + const filteredApprovers = + debouncedSearchTerm !== '' ? tokenizedSearch(data, getSearchValueForPhoneOrEmail(debouncedSearchTerm, countryCode), (option) => [option.text ?? '', option.login ?? '']) : data; + const filteredData = sortAlphabetically(filteredApprovers, 'text', localeCompare); + + return ( + + + Navigation.goBack(ROUTES.DOMAIN_ADMINS_SETTINGS.getRoute(domainAccountID))} + /> + { + if (!option.login || !option.accountID) { + return; + } + if (option.login !== technicalContactEmail) { + setPrimaryContact(domainAccountID, option.accountID, option.login, technicalContactEmail); + } + Navigation.goBack(ROUTES.DOMAIN_ADMINS_SETTINGS.getRoute(domainAccountID)); + }} + ListItem={InviteMemberListItem} + canSelectMultiple={false} + initiallyFocusedItemKey={technicalContactEmailKey} + shouldScrollToFocusedIndex + shouldShowTextInput + textInputOptions={{ + label: translate('selectionList.findMember'), + value: searchTerm, + onChangeText: setSearchTerm, + headerMessage: searchTerm && !filteredData?.length ? translate('common.noResultsFound') : '', + }} + addBottomSafeAreaPadding + showScrollIndicator + /> + + + ); +} + +DomainAddPrimaryContactPage.displayName = 'DomainAddPrimaryContactPage'; + +export default DomainAddPrimaryContactPage; diff --git a/src/pages/domain/Admins/DomainAdminDetailsPage.tsx b/src/pages/domain/Admins/DomainAdminDetailsPage.tsx index 03da639e7d5e..e7007125178e 100644 --- a/src/pages/domain/Admins/DomainAdminDetailsPage.tsx +++ b/src/pages/domain/Admins/DomainAdminDetailsPage.tsx @@ -1,11 +1,8 @@ -import {adminAccountIDsSelector} from '@selectors/Domain'; import {Str} from 'expensify-common'; import React from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import Avatar from '@components/Avatar'; -import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; -import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import MenuItem from '@components/MenuItem'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; @@ -21,13 +18,12 @@ import {getDisplayNameOrDefault, getPhoneNumber} from '@libs/PersonalDetailsUtil import Navigation from '@navigation/Navigation'; import type {PlatformStackScreenProps} from '@navigation/PlatformStackNavigation/types'; import type {SettingsNavigatorParamList} from '@navigation/types'; -import {getCurrentUserAccountID} from '@userActions/Report'; +import DomainNotFoundPageWrapper from '@pages/domain/DomainNotFoundPageWrapper'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type {PersonalDetailsList} from '@src/types/onyx'; -import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; type DomainAdminDetailsPageProps = PlatformStackScreenProps; @@ -37,49 +33,33 @@ function DomainAdminDetailsPage({route}: DomainAdminDetailsPageProps) { const {translate, formatPhoneNumber} = useLocalize(); const icons = useMemoizedLazyExpensifyIcons(['Info'] as const); - const [adminAccountIDs, domainMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.DOMAIN}${domainAccountID}`, { - canBeMissing: true, - selector: adminAccountIDsSelector, - }); - // eslint-disable-next-line rulesdir/no-inline-useOnyx-selector - const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, { + const [adminPersonalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, { canBeMissing: true, selector: (personalDetailsList: OnyxEntry) => personalDetailsList?.[accountID], }); - const displayName = formatPhoneNumber(getDisplayNameOrDefault(personalDetails)); - const memberLogin = personalDetails?.login ?? ''; + const displayName = formatPhoneNumber(getDisplayNameOrDefault(adminPersonalDetails)); + const memberLogin = adminPersonalDetails?.login ?? ''; const isSMSLogin = Str.isSMSLogin(memberLogin); - const phoneNumber = getPhoneNumber(personalDetails); - const fallbackIcon = personalDetails?.fallbackIcon ?? ''; - - const currentUserAccountID = getCurrentUserAccountID(); - const isAdmin = adminAccountIDs?.includes(currentUserAccountID); - - if (isLoadingOnyxValue(domainMetadata)) { - return ; - } + const phoneNumber = getPhoneNumber(adminPersonalDetails); + const fallbackIcon = adminPersonalDetails?.fallbackIcon ?? ''; return ( - - Navigation.goBack(ROUTES.WORKSPACES_LIST.route)} - shouldShow={!isAdmin} - shouldForceFullScreen + + - + - - + + ); } diff --git a/src/pages/domain/Admins/DomainAdminsPage.tsx b/src/pages/domain/Admins/DomainAdminsPage.tsx index edef3780ac63..e29ca899962e 100644 --- a/src/pages/domain/Admins/DomainAdminsPage.tsx +++ b/src/pages/domain/Admins/DomainAdminsPage.tsx @@ -1,6 +1,8 @@ -import {adminAccountIDsSelector} from '@selectors/Domain'; +import {adminAccountIDsSelector, technicalContactEmailSelector} from '@selectors/Domain'; import React from 'react'; -import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; +import {View} from 'react-native'; +import Badge from '@components/Badge'; +import Button from '@components/Button'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; @@ -21,6 +23,7 @@ import tokenizedSearch from '@libs/tokenizedSearch'; import Navigation from '@navigation/Navigation'; import type {PlatformStackScreenProps} from '@navigation/PlatformStackNavigation/types'; import type {DomainSplitNavigatorParamList} from '@navigation/types'; +import DomainNotFoundPageWrapper from '@pages/domain/DomainNotFoundPageWrapper'; import {getCurrentUserAccountID} from '@userActions/Report'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -41,7 +44,7 @@ function DomainAdminsPage({route}: DomainAdminsPageProps) { const {translate, formatPhoneNumber, localeCompare} = useLocalize(); const styles = useThemeStyles(); const illustrations = useMemoizedLazyIllustrations(['Members']); - const icons = useMemoizedLazyExpensifyIcons(['FallbackAvatar']); + const icons = useMemoizedLazyExpensifyIcons(['FallbackAvatar', 'Gear']); const {shouldUseNarrowLayout} = useResponsiveLayout(); const [adminAccountIDs, domainMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.DOMAIN}${domainAccountID}`, { @@ -49,6 +52,13 @@ function DomainAdminsPage({route}: DomainAdminsPageProps) { selector: adminAccountIDsSelector, }); const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {canBeMissing: true}); + const [technicalContactEmail] = useOnyx(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${domainAccountID}`, { + canBeMissing: false, + selector: technicalContactEmailSelector, + }); + + const currentUserAccountID = getCurrentUserAccountID(); + const isAdmin = adminAccountIDs?.includes(currentUserAccountID); const data: AdminOption[] = []; for (const accountID of adminAccountIDs ?? []) { @@ -67,6 +77,7 @@ function DomainAdminsPage({route}: DomainAdminsPageProps) { id: accountID, }, ], + rightElement: technicalContactEmail === details?.login && , }); } @@ -90,6 +101,25 @@ function DomainAdminsPage({route}: DomainAdminsPageProps) { ); }; + const getHeaderButtons = () => { + if (!isAdmin) { + return null; + } + return ( + +