diff --git a/src/api/gramjs/methods/index.ts b/src/api/gramjs/methods/index.ts index be17d239f..79d3f4ddb 100644 --- a/src/api/gramjs/methods/index.ts +++ b/src/api/gramjs/methods/index.ts @@ -28,7 +28,7 @@ export { export { fetchFullUser, fetchNearestCountry, fetchCountryList, fetchTopUsers, fetchContactList, fetchUsers, - updateContact, deleteUser, fetchProfilePhotos, + addContact, updateContact, deleteUser, fetchProfilePhotos, } from './users'; export { diff --git a/src/api/gramjs/methods/users.ts b/src/api/gramjs/methods/users.ts index e940318eb..19121ec01 100644 --- a/src/api/gramjs/methods/users.ts +++ b/src/api/gramjs/methods/users.ts @@ -149,6 +149,27 @@ export function updateContact({ })); } +export function addContact({ + id, + accessHash, + phoneNumber = '', + firstName = '', + lastName = '', +}: { + id: number; + accessHash?: string; + phoneNumber?: string; + firstName?: string; + lastName?: string; +}) { + return invokeRequest(new GramJs.contacts.AddContact({ + id: buildInputEntity(id, accessHash) as GramJs.InputUser, + firstName, + lastName, + phone: phoneNumber, + }), true); +} + export async function deleteUser({ id, accessHash, diff --git a/src/components/middle/HeaderMenuContainer.tsx b/src/components/middle/HeaderMenuContainer.tsx index 08b6d620b..0d104842c 100644 --- a/src/components/middle/HeaderMenuContainer.tsx +++ b/src/components/middle/HeaderMenuContainer.tsx @@ -9,9 +9,13 @@ import { IAnchorPosition } from '../../types'; import { IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment'; import { disableScrolling, enableScrolling } from '../../util/scrollLock'; -import { selectChat, selectNotifySettings, selectNotifyExceptions } from '../../modules/selectors'; +import { + selectChat, selectNotifySettings, selectNotifyExceptions, selectUser, +} from '../../modules/selectors'; import { pick } from '../../util/iteratees'; -import { isChatPrivate, getCanDeleteChat, selectIsChatMuted } from '../../modules/helpers'; +import { + isChatPrivate, getCanDeleteChat, selectIsChatMuted, getCanAddContact, +} from '../../modules/helpers'; import useShowTransition from '../../hooks/useShowTransition'; import useLang from '../../hooks/useLang'; @@ -23,7 +27,7 @@ import DeleteChatModal from '../common/DeleteChatModal'; import './HeaderMenuContainer.scss'; type DispatchProps = Pick; export type OwnProps = { @@ -48,6 +52,7 @@ type StateProps = { chat?: ApiChat; isPrivate?: boolean; isMuted?: boolean; + canAddContact?: boolean; canDeleteChat?: boolean; hasLinkedChat?: boolean; }; @@ -68,6 +73,7 @@ const HeaderMenuContainer: FC = ({ isMuted, canDeleteChat, hasLinkedChat, + canAddContact, onSubscribeChannel, onSearchClick, onClose, @@ -77,6 +83,7 @@ const HeaderMenuContainer: FC = ({ sendBotCommand, restartBot, openLinkedChat, + addContact, }) => { const [isMenuOpen, setIsMenuOpen] = useState(true); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); @@ -117,6 +124,11 @@ const HeaderMenuContainer: FC = ({ closeMenu(); }, [chatId, closeMenu, openLinkedChat]); + const handleAddContactClick = useCallback(() => { + addContact({ userId: chatId }); + closeMenu(); + }, [addContact, chatId, closeMenu]); + const handleSubscribe = useCallback(() => { onSubscribeChannel(); closeMenu(); @@ -173,6 +185,14 @@ const HeaderMenuContainer: FC = ({ {lang(isChannel ? 'Subscribe' : 'Join Group')} )} + {canAddContact && ( + + {lang('AddContact')} + + )} {IS_SINGLE_COLUMN_LAYOUT && canSearch && ( ( if (!chat || chat.isRestricted) { return {}; } + const isPrivate = isChatPrivate(chat.id); + const user = isPrivate ? selectUser(global, chatId) : undefined; + const canAddContact = user && getCanAddContact(user); return { chat, isMuted: selectIsChatMuted(chat, selectNotifySettings(global), selectNotifyExceptions(global)), - isPrivate: isChatPrivate(chat.id), + isPrivate, + canAddContact, canDeleteChat: getCanDeleteChat(chat), hasLinkedChat: Boolean(chat?.fullInfo?.linkedChatId), }; @@ -248,5 +272,6 @@ export default memo(withGlobal( 'sendBotCommand', 'restartBot', 'openLinkedChat', + 'addContact', ]), )(HeaderMenuContainer)); diff --git a/src/components/right/RightHeader.tsx b/src/components/right/RightHeader.tsx index 1a7ca7ec7..5ca72cdc5 100644 --- a/src/components/right/RightHeader.tsx +++ b/src/components/right/RightHeader.tsx @@ -16,8 +16,14 @@ import { selectCurrentStickerSearch, selectCurrentTextSearch, selectIsChatWithSelf, + selectUser, } from '../../modules/selectors'; -import { isChatAdmin, isChatChannel, isChatPrivate } from '../../modules/helpers'; +import { + getCanAddContact, + isChatAdmin, + isChatChannel, + isChatPrivate, +} from '../../modules/helpers'; import useCurrentOrPrev from '../../hooks/useCurrentOrPrev'; import useLang from '../../hooks/useLang'; @@ -44,8 +50,10 @@ type OwnProps = { }; type StateProps = { + canAddContact?: boolean; canManage?: boolean; isChannel?: boolean; + userId?: number; messageSearchQuery?: string; stickerSearchQuery?: string; gifSearchQuery?: string; @@ -53,7 +61,7 @@ type StateProps = { type DispatchProps = Pick; const COLUMN_CLOSE_DELAY_MS = 300; @@ -94,6 +102,8 @@ const RightHeader: FC = ({ isAddingChatMembers, profileState, managementScreen, + canAddContact, + userId, canManage, isChannel, onClose, @@ -107,6 +117,7 @@ const RightHeader: FC = ({ toggleManagement, openHistoryCalendar, shouldSkipAnimation, + addContact, }) => { // eslint-disable-next-line no-null/no-null const backButtonRef = useRef(null); @@ -127,6 +138,10 @@ const RightHeader: FC = ({ setGifSearchQuery({ query }); }, [setGifSearchQuery]); + const handleAddContact = useCallback(() => { + addContact({ userId }); + }, [addContact, userId]); + const [shouldSkipTransition, setShouldSkipTransition] = useState(!isColumnOpen); useEffect(() => { @@ -263,6 +278,17 @@ const RightHeader: FC = ({ <>

Profile

+ {canAddContact && ( + + )} {canManage && (