1 import { useMemo, useState } from 'react';
3 import { c, msgid } from 'ttag';
5 import { useMemberAddresses } from '@proton/account';
6 import { useAddresses } from '@proton/account/addresses/hooks';
7 import { useCustomDomains } from '@proton/account/domains/hooks';
8 import { useMembers } from '@proton/account/members/hooks';
9 import { getDomainAddressError } from '@proton/account/members/validateAddUser';
10 import { useOrganizationKey } from '@proton/account/organizationKey/hooks';
11 import { useProtonDomains } from '@proton/account/protonDomains/hooks';
12 import { Button, Href } from '@proton/atoms';
13 import Alert from '@proton/components/components/alert/Alert';
14 import SettingsLink from '@proton/components/components/link/SettingsLink';
15 import Loader from '@proton/components/components/loader/Loader';
16 import useModalState from '@proton/components/components/modalTwo/useModalState';
17 import SettingsParagraph from '@proton/components/containers/account/SettingsParagraph';
18 import GenericError from '@proton/components/containers/error/GenericError';
19 import useNotifications from '@proton/components/hooks/useNotifications';
20 import { ALL_MEMBERS_ID, BRAND_NAME, MEMBER_PRIVATE } from '@proton/shared/lib/constants';
21 import { getAvailableAddressDomains } from '@proton/shared/lib/helpers/address';
22 import { getKnowledgeBaseUrl } from '@proton/shared/lib/helpers/url';
23 import type { Member, Organization, UserModel } from '@proton/shared/lib/interfaces';
24 import { getOrganizationKeyInfo, validateOrganizationKey } from '@proton/shared/lib/organization/helper';
26 import AddressModal from './AddressModal';
27 import AddressesTable from './AddressesTable';
28 import AddressesWithUser from './AddressesWithUser';
30 const getMemberIndex = (members: Member[] = [], memberID?: string, isOnlySelf?: boolean) => {
31 const newMemberIndex =
32 memberID && !isOnlySelf
33 ? members.findIndex(({ ID }) => ID === memberID)
34 : members.findIndex(({ Self }) => Self);
35 if (newMemberIndex === -1 && members.length) {
38 return newMemberIndex;
43 organization?: Organization;
46 allowAddressDeletion: boolean;
47 hasDescription?: boolean;
50 const AddressesWithMembers = ({
56 hasDescription = true,
58 const [members, loadingMembers] = useMembers();
59 const [addresses, loadingAddresses] = useAddresses();
60 const [customDomains] = useCustomDomains();
61 const [{ premiumDomains, protonDomains }] = useProtonDomains();
62 const [organizationKey] = useOrganizationKey();
63 const [addressModalProps, setAddressModalOpen, renderAddressModal] = useModalState();
64 const { createNotification } = useNotifications();
65 const [tmpMember, setTmpMember] = useState<Member | null>(null);
67 const hasAddresses = Array.isArray(addresses) && addresses.length > 0;
69 const { UsedAddresses: OrganizationUsedAddresses, MaxAddresses: OrganizationMaxAddresses } = organization || {};
70 const UsedAddresses = hasAddresses ? OrganizationUsedAddresses || 1 : 0;
71 const MaxAddresses = OrganizationMaxAddresses || 1;
73 const memberIndex = useMemo(() => {
74 if (Array.isArray(members)) {
75 return getMemberIndex(members, memberID, isOnlySelf);
79 }, [members, memberID]);
81 const selectedMembers = useMemo(() => {
82 if (members && memberIndex === ALL_MEMBERS_ID) {
85 if (members && memberIndex in members) {
86 return [members[memberIndex]];
89 }, [members, memberIndex]);
91 const { value: memberAddressesMap, retry } = useMemberAddresses({
92 members: selectedMembers,
96 const hasUsernameDisplay = memberIndex === ALL_MEMBERS_ID;
97 const isSelfSelected = useMemo(() => {
101 return memberIndex === members.findIndex(({ Self }) => Self);
102 }, [memberIndex, members]);
104 const handleAddAddress = (member: Member) => {
105 if (member.Private === MEMBER_PRIVATE.READABLE) {
106 const orgKeyError = validateOrganizationKey(
107 getOrganizationKeyInfo(organization, organizationKey, addresses)
110 createNotification({ type: 'error', text: orgKeyError });
114 const domains = getAvailableAddressDomains({
121 if (!domains.length) {
122 createNotification({ type: 'error', text: getDomainAddressError() });
125 setTmpMember(member);
126 setAddressModalOpen(true);
129 const currentMember = members?.[memberIndex];
131 const mustActivateOrganizationKey =
132 currentMember?.Private === MEMBER_PRIVATE.READABLE && !organizationKey?.privateKey;
134 const activateLink = (
135 <SettingsLink path="/organization-keys" key="activate">{c('Action').t`activate`}</SettingsLink>
138 const membersWithError = selectedMembers.filter((member) => {
139 return member.addressState === 'rejected';
145 <SettingsParagraph className="mt-2">
147 {c('Info').t`Use the different types of email addresses and aliases offered by ${BRAND_NAME}.`}
150 <Href href={getKnowledgeBaseUrl('/addresses-and-aliases')}>{c('Link').t`Learn more`}</Href>
154 {currentMember && !user.isSubUser && (
155 <div className="mb-4 flex gap-2 self-start items-center">
156 <div className="mr-4">
157 {mustActivateOrganizationKey ? (
158 <Alert className="mb-4" type="warning">
160 .jt`You must ${activateLink} the organization key before adding an email address to a non-private member.`}
165 onClick={() => handleAddAddress(currentMember)}
166 data-testid="settings:identity-section:add-address"
168 {c('Action').t`Add address`}
173 {c('Label').ngettext(
174 msgid`${UsedAddresses} of ${MaxAddresses} email address`,
175 `${UsedAddresses} of ${MaxAddresses} email addresses`,
183 if (isSelfSelected) {
187 member={currentMember}
188 organizationKey={organizationKey}
189 hasDescription={false}
190 allowAddressDeletion={allowAddressDeletion}
195 if (membersWithError.length) {
198 <Button onClick={() => retry(membersWithError)}>{c('Action').t`Retry`}</Button>
205 hasUsername={hasUsernameDisplay}
206 loading={selectedMembers.some(({ ID }) => !Array.isArray(memberAddressesMap?.[ID]))}
208 members={selectedMembers}
209 memberAddressesMap={memberAddressesMap}
210 organizationKey={organizationKey}
211 allowAddressDeletion={allowAddressDeletion}
217 const loading = loadingMembers || loadingAddresses || memberIndex === -1;
221 {renderAddressModal && tmpMember && members && (
222 <AddressModal member={tmpMember} members={members} {...addressModalProps} />
224 {loading ? <Loader /> : children}
229 export default AddressesWithMembers;