2021-04-30 19:40:25 +00:00
|
|
|
// Copyright 2020-2021 Signal Messenger, LLC
|
2020-11-11 17:36:05 +00:00
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
2021-09-21 22:37:10 +00:00
|
|
|
import React, { useEffect, useState } from 'react';
|
2020-11-11 17:36:05 +00:00
|
|
|
|
2021-11-02 23:01:13 +00:00
|
|
|
import { missingCaseError } from '../../util/missingCaseError';
|
2021-01-26 01:01:19 +00:00
|
|
|
import { About } from './About';
|
2020-11-11 17:36:05 +00:00
|
|
|
import { Avatar } from '../Avatar';
|
2021-08-06 00:17:05 +00:00
|
|
|
import { AvatarLightbox } from '../AvatarLightbox';
|
2021-10-26 19:15:33 +00:00
|
|
|
import type { ConversationType } from '../../state/ducks/conversations';
|
2021-09-21 22:37:10 +00:00
|
|
|
import { Modal } from '../Modal';
|
2021-10-26 19:15:33 +00:00
|
|
|
import type { LocalizerType } from '../../types/Util';
|
2021-11-02 23:01:13 +00:00
|
|
|
import { BadgeDialog } from '../BadgeDialog';
|
|
|
|
import type { BadgeType } from '../../badges/types';
|
2021-08-06 00:17:05 +00:00
|
|
|
import { SharedGroupNames } from '../SharedGroupNames';
|
2021-09-21 22:37:10 +00:00
|
|
|
import { ConfirmationDialog } from '../ConfirmationDialog';
|
2021-11-09 15:51:56 +00:00
|
|
|
import { shouldShowBadges } from '../../badges/shouldShowBadges';
|
2020-11-11 17:36:05 +00:00
|
|
|
|
2021-09-21 22:37:10 +00:00
|
|
|
export type PropsDataType = {
|
2020-11-11 17:36:05 +00:00
|
|
|
areWeAdmin: boolean;
|
2021-11-02 23:01:13 +00:00
|
|
|
badges: ReadonlyArray<BadgeType>;
|
2020-11-11 17:36:05 +00:00
|
|
|
contact?: ConversationType;
|
2021-09-21 22:37:10 +00:00
|
|
|
conversationId?: string;
|
2020-11-11 17:36:05 +00:00
|
|
|
readonly i18n: LocalizerType;
|
2021-01-29 21:19:24 +00:00
|
|
|
isAdmin: boolean;
|
2020-11-11 17:36:05 +00:00
|
|
|
isMember: boolean;
|
|
|
|
};
|
|
|
|
|
2021-09-21 22:37:10 +00:00
|
|
|
type PropsActionType = {
|
|
|
|
hideContactModal: () => void;
|
|
|
|
openConversationInternal: (
|
|
|
|
options: Readonly<{
|
|
|
|
conversationId: string;
|
|
|
|
messageId?: string;
|
|
|
|
switchToAssociatedView?: boolean;
|
|
|
|
}>
|
|
|
|
) => void;
|
|
|
|
removeMemberFromGroup: (conversationId: string, contactId: string) => void;
|
|
|
|
toggleAdmin: (conversationId: string, contactId: string) => void;
|
2021-10-07 21:45:58 +00:00
|
|
|
toggleSafetyNumberModal: (conversationId: string) => unknown;
|
2021-09-21 22:37:10 +00:00
|
|
|
updateConversationModelSharedGroups: (conversationId: string) => void;
|
|
|
|
};
|
|
|
|
|
|
|
|
export type PropsType = PropsDataType & PropsActionType;
|
|
|
|
|
2021-11-02 23:01:13 +00:00
|
|
|
enum ContactModalView {
|
|
|
|
Default,
|
|
|
|
ShowingAvatar,
|
|
|
|
ShowingBadges,
|
|
|
|
}
|
|
|
|
|
2020-11-11 17:36:05 +00:00
|
|
|
export const ContactModal = ({
|
|
|
|
areWeAdmin,
|
2021-11-02 23:01:13 +00:00
|
|
|
badges,
|
2020-11-11 17:36:05 +00:00
|
|
|
contact,
|
2021-09-21 22:37:10 +00:00
|
|
|
conversationId,
|
|
|
|
hideContactModal,
|
2020-11-11 17:36:05 +00:00
|
|
|
i18n,
|
2021-01-29 21:19:24 +00:00
|
|
|
isAdmin,
|
2020-11-11 17:36:05 +00:00
|
|
|
isMember,
|
2021-09-21 22:37:10 +00:00
|
|
|
openConversationInternal,
|
|
|
|
removeMemberFromGroup,
|
2021-01-29 21:19:24 +00:00
|
|
|
toggleAdmin,
|
2021-10-07 21:45:58 +00:00
|
|
|
toggleSafetyNumberModal,
|
2021-09-21 22:37:10 +00:00
|
|
|
updateConversationModelSharedGroups,
|
|
|
|
}: PropsType): JSX.Element => {
|
2020-11-11 17:36:05 +00:00
|
|
|
if (!contact) {
|
|
|
|
throw new Error('Contact modal opened without a matching contact');
|
|
|
|
}
|
|
|
|
|
2021-11-02 23:01:13 +00:00
|
|
|
const [view, setView] = useState(ContactModalView.Default);
|
2021-09-21 22:37:10 +00:00
|
|
|
const [confirmToggleAdmin, setConfirmToggleAdmin] = useState(false);
|
2020-11-11 17:36:05 +00:00
|
|
|
|
2021-08-06 00:17:05 +00:00
|
|
|
useEffect(() => {
|
2021-09-21 22:37:10 +00:00
|
|
|
if (conversationId) {
|
|
|
|
// Kick off the expensive hydration of the current sharedGroupNames
|
|
|
|
updateConversationModelSharedGroups(conversationId);
|
2020-11-11 17:36:05 +00:00
|
|
|
}
|
2021-09-21 22:37:10 +00:00
|
|
|
}, [conversationId, updateConversationModelSharedGroups]);
|
2020-11-11 17:36:05 +00:00
|
|
|
|
2021-11-02 23:01:13 +00:00
|
|
|
switch (view) {
|
|
|
|
case ContactModalView.Default: {
|
|
|
|
const preferredBadge: undefined | BadgeType = badges[0];
|
2021-09-21 22:37:10 +00:00
|
|
|
|
2021-11-02 23:01:13 +00:00
|
|
|
return (
|
|
|
|
<Modal
|
|
|
|
moduleClassName="ContactModal__modal"
|
|
|
|
hasXButton
|
2021-08-06 00:17:05 +00:00
|
|
|
i18n={i18n}
|
2021-11-02 23:01:13 +00:00
|
|
|
onClose={hideContactModal}
|
|
|
|
>
|
|
|
|
<div className="ContactModal">
|
|
|
|
<Avatar
|
|
|
|
acceptedMessageRequest={contact.acceptedMessageRequest}
|
|
|
|
avatarPath={contact.avatarPath}
|
|
|
|
badge={preferredBadge}
|
|
|
|
color={contact.color}
|
|
|
|
conversationType="direct"
|
2021-09-21 22:37:10 +00:00
|
|
|
i18n={i18n}
|
2021-11-02 23:01:13 +00:00
|
|
|
isMe={contact.isMe}
|
|
|
|
name={contact.name}
|
|
|
|
profileName={contact.profileName}
|
|
|
|
sharedGroupNames={contact.sharedGroupNames}
|
|
|
|
size={96}
|
|
|
|
title={contact.title}
|
|
|
|
unblurredAvatarPath={contact.unblurredAvatarPath}
|
2021-09-21 22:37:10 +00:00
|
|
|
onClick={() => {
|
2021-11-02 23:01:13 +00:00
|
|
|
setView(
|
2021-11-09 15:51:56 +00:00
|
|
|
preferredBadge && shouldShowBadges()
|
2021-11-02 23:01:13 +00:00
|
|
|
? ContactModalView.ShowingBadges
|
|
|
|
: ContactModalView.ShowingAvatar
|
|
|
|
);
|
2021-09-21 22:37:10 +00:00
|
|
|
}}
|
2021-11-02 23:01:13 +00:00
|
|
|
/>
|
|
|
|
<div className="ContactModal__name">{contact.title}</div>
|
|
|
|
<div className="module-about__container">
|
|
|
|
<About text={contact.about} />
|
|
|
|
</div>
|
|
|
|
{contact.phoneNumber && (
|
|
|
|
<div className="ContactModal__info">{contact.phoneNumber}</div>
|
|
|
|
)}
|
|
|
|
{!contact.isMe && (
|
|
|
|
<div className="ContactModal__info">
|
|
|
|
<SharedGroupNames
|
|
|
|
i18n={i18n}
|
|
|
|
sharedGroupNames={contact.sharedGroupNames || []}
|
|
|
|
/>
|
2020-11-11 17:36:05 +00:00
|
|
|
</div>
|
2021-11-02 23:01:13 +00:00
|
|
|
)}
|
|
|
|
<div className="ContactModal__button-container">
|
2020-11-11 17:36:05 +00:00
|
|
|
<button
|
|
|
|
type="button"
|
2021-11-02 23:01:13 +00:00
|
|
|
className="ContactModal__button ContactModal__send-message"
|
|
|
|
onClick={() => {
|
|
|
|
hideContactModal();
|
|
|
|
openConversationInternal({ conversationId: contact.id });
|
|
|
|
}}
|
2020-11-11 17:36:05 +00:00
|
|
|
>
|
2021-09-21 22:37:10 +00:00
|
|
|
<div className="ContactModal__bubble-icon">
|
2021-11-02 23:01:13 +00:00
|
|
|
<div className="ContactModal__send-message__bubble-icon" />
|
2020-11-11 17:36:05 +00:00
|
|
|
</div>
|
2021-11-02 23:01:13 +00:00
|
|
|
<span>{i18n('ContactModal--message')}</span>
|
2020-11-11 17:36:05 +00:00
|
|
|
</button>
|
2021-11-02 23:01:13 +00:00
|
|
|
{!contact.isMe && (
|
|
|
|
<button
|
|
|
|
type="button"
|
|
|
|
className="ContactModal__button ContactModal__safety-number"
|
|
|
|
onClick={() => {
|
|
|
|
hideContactModal();
|
|
|
|
toggleSafetyNumberModal(contact.id);
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<div className="ContactModal__bubble-icon">
|
|
|
|
<div className="ContactModal__safety-number__bubble-icon" />
|
|
|
|
</div>
|
|
|
|
<span>{i18n('showSafetyNumber')}</span>
|
|
|
|
</button>
|
|
|
|
)}
|
|
|
|
{!contact.isMe && areWeAdmin && isMember && conversationId && (
|
|
|
|
<>
|
|
|
|
<button
|
|
|
|
type="button"
|
|
|
|
className="ContactModal__button ContactModal__make-admin"
|
|
|
|
onClick={() => setConfirmToggleAdmin(true)}
|
|
|
|
>
|
|
|
|
<div className="ContactModal__bubble-icon">
|
|
|
|
<div className="ContactModal__make-admin__bubble-icon" />
|
|
|
|
</div>
|
|
|
|
{isAdmin ? (
|
|
|
|
<span>{i18n('ContactModal--rm-admin')}</span>
|
|
|
|
) : (
|
|
|
|
<span>{i18n('ContactModal--make-admin')}</span>
|
|
|
|
)}
|
|
|
|
</button>
|
|
|
|
<button
|
|
|
|
type="button"
|
|
|
|
className="ContactModal__button ContactModal__remove-from-group"
|
|
|
|
onClick={() =>
|
|
|
|
removeMemberFromGroup(conversationId, contact.id)
|
|
|
|
}
|
|
|
|
>
|
|
|
|
<div className="ContactModal__bubble-icon">
|
|
|
|
<div className="ContactModal__remove-from-group__bubble-icon" />
|
|
|
|
</div>
|
|
|
|
<span>{i18n('ContactModal--remove-from-group')}</span>
|
|
|
|
</button>
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
{confirmToggleAdmin && conversationId && (
|
|
|
|
<ConfirmationDialog
|
|
|
|
actions={[
|
|
|
|
{
|
|
|
|
action: () => toggleAdmin(conversationId, contact.id),
|
|
|
|
text: isAdmin
|
|
|
|
? i18n('ContactModal--rm-admin')
|
|
|
|
: i18n('ContactModal--make-admin'),
|
|
|
|
},
|
|
|
|
]}
|
|
|
|
i18n={i18n}
|
|
|
|
onClose={() => setConfirmToggleAdmin(false)}
|
2021-08-06 00:17:05 +00:00
|
|
|
>
|
2021-11-02 23:01:13 +00:00
|
|
|
{isAdmin
|
|
|
|
? i18n('ContactModal--rm-admin-info', [contact.title])
|
|
|
|
: i18n('ContactModal--make-admin-info', [contact.title])}
|
|
|
|
</ConfirmationDialog>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
</Modal>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
case ContactModalView.ShowingAvatar:
|
|
|
|
return (
|
|
|
|
<AvatarLightbox
|
|
|
|
avatarColor={contact.color}
|
|
|
|
avatarPath={contact.avatarPath}
|
|
|
|
conversationTitle={contact.title}
|
|
|
|
i18n={i18n}
|
|
|
|
onClose={() => setView(ContactModalView.Default)}
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
case ContactModalView.ShowingBadges:
|
|
|
|
return (
|
|
|
|
<BadgeDialog
|
|
|
|
badges={badges}
|
|
|
|
firstName={contact.firstName}
|
|
|
|
i18n={i18n}
|
|
|
|
onClose={() => setView(ContactModalView.Default)}
|
|
|
|
title={contact.title}
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
default:
|
|
|
|
throw missingCaseError(view);
|
|
|
|
}
|
2020-11-11 17:36:05 +00:00
|
|
|
};
|