Remove payments API routing initialization
[ProtonMail-WebClient.git] / packages / components / containers / addresses / AddressesWithMembers.tsx
blobc0cce9a47042ca8edfb93c1328f2a437d15b9bb7
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) {
36         return 0;
37     }
38     return newMemberIndex;
41 interface Props {
42     user: UserModel;
43     organization?: Organization;
44     isOnlySelf?: boolean;
45     memberID?: string;
46     allowAddressDeletion: boolean;
47     hasDescription?: boolean;
50 const AddressesWithMembers = ({
51     user,
52     organization,
53     memberID,
54     isOnlySelf,
55     allowAddressDeletion,
56     hasDescription = true,
57 }: Props) => {
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);
76         }
78         return -1;
79     }, [members, memberID]);
81     const selectedMembers = useMemo(() => {
82         if (members && memberIndex === ALL_MEMBERS_ID) {
83             return members;
84         }
85         if (members && memberIndex in members) {
86             return [members[memberIndex]];
87         }
88         return [];
89     }, [members, memberIndex]);
91     const { value: memberAddressesMap, retry } = useMemberAddresses({
92         members: selectedMembers,
93         partial: false,
94     });
96     const hasUsernameDisplay = memberIndex === ALL_MEMBERS_ID;
97     const isSelfSelected = useMemo(() => {
98         if (!members) {
99             return false;
100         }
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)
108             );
109             if (orgKeyError) {
110                 createNotification({ type: 'error', text: orgKeyError });
111                 return;
112             }
113         }
114         const domains = getAvailableAddressDomains({
115             member,
116             user,
117             premiumDomains,
118             customDomains,
119             protonDomains,
120         });
121         if (!domains.length) {
122             createNotification({ type: 'error', text: getDomainAddressError() });
123             return;
124         }
125         setTmpMember(member);
126         setAddressModalOpen(true);
127     };
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>
136     );
138     const membersWithError = selectedMembers.filter((member) => {
139         return member.addressState === 'rejected';
140     });
142     const children = (
143         <>
144             {hasDescription && (
145                 <SettingsParagraph className="mt-2">
146                     <span>
147                         {c('Info').t`Use the different types of email addresses and aliases offered by ${BRAND_NAME}.`}
148                     </span>
149                     <br />
150                     <Href href={getKnowledgeBaseUrl('/addresses-and-aliases')}>{c('Link').t`Learn more`}</Href>
151                 </SettingsParagraph>
152             )}
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">
159                                 {c('Warning')
160                                     .jt`You must ${activateLink} the organization key before adding an email address to a non-private member.`}
161                             </Alert>
162                         ) : (
163                             <Button
164                                 shape="outline"
165                                 onClick={() => handleAddAddress(currentMember)}
166                                 data-testid="settings:identity-section:add-address"
167                             >
168                                 {c('Action').t`Add address`}
169                             </Button>
170                         )}
171                     </div>
172                     <div>
173                         {c('Label').ngettext(
174                             msgid`${UsedAddresses} of ${MaxAddresses} email address`,
175                             `${UsedAddresses} of ${MaxAddresses} email addresses`,
176                             MaxAddresses
177                         )}
178                     </div>
179                 </div>
180             )}
182             {(() => {
183                 if (isSelfSelected) {
184                     return (
185                         <AddressesWithUser
186                             user={user}
187                             member={currentMember}
188                             organizationKey={organizationKey}
189                             hasDescription={false}
190                             allowAddressDeletion={allowAddressDeletion}
191                         />
192                     );
193                 }
195                 if (membersWithError.length) {
196                     return (
197                         <GenericError>
198                             <Button onClick={() => retry(membersWithError)}>{c('Action').t`Retry`}</Button>
199                         </GenericError>
200                     );
201                 }
203                 return (
204                     <AddressesTable
205                         hasUsername={hasUsernameDisplay}
206                         loading={selectedMembers.some(({ ID }) => !Array.isArray(memberAddressesMap?.[ID]))}
207                         user={user}
208                         members={selectedMembers}
209                         memberAddressesMap={memberAddressesMap}
210                         organizationKey={organizationKey}
211                         allowAddressDeletion={allowAddressDeletion}
212                     />
213                 );
214             })()}
215         </>
216     );
217     const loading = loadingMembers || loadingAddresses || memberIndex === -1;
219     return (
220         <>
221             {renderAddressModal && tmpMember && members && (
222                 <AddressModal member={tmpMember} members={members} {...addressModalProps} />
223             )}
224             {loading ? <Loader /> : children}
225         </>
226     );
229 export default AddressesWithMembers;