1 import { useMemo } from 'react';
3 import { c } from 'ttag';
5 import { useUser } from '@proton/account/user/hooks';
6 import { Avatar, CircleLoader } from '@proton/atoms';
7 import { useSortedList } from '@proton/components';
8 import useLoading from '@proton/hooks/useLoading';
9 import { useContactEmails } from '@proton/mail/contactEmails/hooks';
10 import { SORT_DIRECTION } from '@proton/shared/lib/constants';
11 import type { SHARE_MEMBER_PERMISSIONS } from '@proton/shared/lib/drive/permissions';
12 import { canonicalizeEmailByGuess } from '@proton/shared/lib/helpers/email';
13 import { getInitials } from '@proton/shared/lib/helpers/string';
14 import type { ContactEmail } from '@proton/shared/lib/interfaces/contacts';
16 import type { ShareExternalInvitation, ShareInvitation, ShareMember } from '../../../../store';
17 import { DirectSharingListInvitation } from './DirectSharingListInvitation';
18 import { MemberDropdownMenu } from './MemberDropdownMenu';
23 members: ShareMember[];
24 invitations: ShareInvitation[];
25 externalInvitations: ShareExternalInvitation[];
27 onPermissionsChange: (member: ShareMember, permission: SHARE_MEMBER_PERMISSIONS) => Promise<void>;
28 onInvitationPermissionsChange: (invitationId: string, permission: SHARE_MEMBER_PERMISSIONS) => Promise<void>;
29 onExternalInvitationPermissionsChange: (
30 externalInvitationId: string,
31 permission: SHARE_MEMBER_PERMISSIONS
33 onMemberRemove: (member: ShareMember) => Promise<void>;
34 onInvitationRemove: (invitationId: string) => Promise<void>;
35 onExternalInvitationRemove: (externalInvitationId: string) => Promise<void>;
36 onResendInvitationEmail: (invitationId: string) => Promise<void>;
37 onResendExternalInvitationEmail: (externaInvitationId: string) => Promise<void>;
40 const getContactNameAndEmail = (email: string, contactEmails?: ContactEmail[]) => {
41 const canonicalizedEmail = canonicalizeEmailByGuess(email);
42 const { Name: contactName, Email: contactEmail } = contactEmails?.find(
43 (contactEmail) => canonicalizeEmailByGuess(contactEmail.Email) === canonicalizedEmail
65 onPermissionsChange: (member: ShareMember, permission: SHARE_MEMBER_PERMISSIONS) => Promise<void>;
66 onMemberRemove: (member: ShareMember) => Promise<void>;
68 const [isLoading, withIsLoading] = useLoading(false);
69 const { memberId, permissions } = member;
70 const handlePermissionChange = (value: SHARE_MEMBER_PERMISSIONS) =>
71 withIsLoading(onPermissionsChange(member, value));
73 const handleMemberRemove = () => withIsLoading(onMemberRemove(member));
78 className="flex my-4 justify-space-between items-center"
79 data-testid="share-accepted-members"
81 <div className={'flex items-center'}>
82 <Avatar color="weak" className="mr-2">
83 {getInitials(contactName || contactEmail)}
85 <p className="flex flex-column p-0 m-0">
86 <span className="text-semibold">{contactName ? contactName : contactEmail}</span>
87 {contactName && <span className="color-weak">{contactEmail}</span>}
92 selectedPermissions={permissions}
93 onChangePermissions={handlePermissionChange}
94 onRemoveAccess={handleMemberRemove}
100 export const DirectSharingListing = ({
110 onInvitationPermissionsChange,
111 onExternalInvitationRemove,
112 onExternalInvitationPermissionsChange,
113 onResendInvitationEmail,
114 onResendExternalInvitationEmail,
116 const [user] = useUser();
117 const [contactEmails] = useContactEmails();
119 const displayName = user.DisplayName || user.Name;
121 const membersWithName = useMemo(
123 members.map((member) => {
124 const { contactName, contactEmail } = getContactNameAndEmail(member.email, contactEmails);
125 return { member, contactName, contactEmail };
127 [members, contactEmails]
129 const { sortedList: sortedMembersWithName } = useSortedList(membersWithName, {
131 direction: SORT_DIRECTION.ASC,
135 return <CircleLoader size="medium" className="mx-auto my-6 w-full" />;
139 <div className="flex my-4 justify-space-between items-center" data-testid="share-owner">
140 <div className={'flex items-center'}>
141 <Avatar color="weak" className="mr-2">
142 {getInitials(displayName || user.Email)}
144 <p className="flex flex-column p-0 m-0">
145 <span className="text-semibold">
146 {displayName} ({c('Info').t`you`})
148 <span className="color-weak">{user.Email}</span>
151 <div className="mr-8">{c('Info').t`Owner`}</div>
155 externalInvitations.map((externalInvitation) => {
156 const { contactName, contactEmail } = getContactNameAndEmail(
157 externalInvitation.inviteeEmail,
161 <DirectSharingListInvitation
162 key={externalInvitation.externalInvitationId}
163 invitationId={externalInvitation.externalInvitationId}
166 contactName={contactName}
167 contactEmail={contactEmail}
168 selectedPermissions={externalInvitation.permissions}
169 onInvitationRemove={onExternalInvitationRemove}
170 onInvitationPermissionsChange={onExternalInvitationPermissionsChange}
171 onResendInvitationEmail={onResendExternalInvitationEmail}
172 externalInvitationState={externalInvitation.state}
177 invitations.map((invitation) => {
178 const { contactName, contactEmail } = getContactNameAndEmail(
179 invitation.inviteeEmail,
183 <DirectSharingListInvitation
184 key={invitation.invitationId}
185 invitationId={invitation.invitationId}
188 contactName={contactName}
189 contactEmail={contactEmail}
190 selectedPermissions={invitation.permissions}
191 onInvitationRemove={onInvitationRemove}
192 onInvitationPermissionsChange={onInvitationPermissionsChange}
193 onResendInvitationEmail={onResendInvitationEmail}
198 {sortedMembersWithName.map(({ member, contactName, contactEmail }) => {
201 key={member.memberId}
203 contactName={contactName}
204 contactEmail={contactEmail}
205 onPermissionsChange={onPermissionsChange}
206 onMemberRemove={onMemberRemove}