New Contact: Allow to rename and add phone number (#1777)

This commit is contained in:
Alexander Zinchuk 2022-03-19 21:20:02 +01:00
parent 1c50550079
commit 6d063c2132
18 changed files with 436 additions and 39 deletions

View File

@ -2,6 +2,9 @@
"extends": [
"stylelint-config-recommended-scss"
],
"ignoreFiles": [
"dist/*.css"
],
"plugins": [
"stylelint-declaration-block-no-ignored-properties",
"stylelint-high-performance-animation",

View File

@ -29,7 +29,7 @@ export {
export {
fetchFullUser, fetchNearestCountry, fetchTopUsers, fetchContactList, fetchUsers,
addContact, updateContact, deleteContact, fetchProfilePhotos, fetchCommonChats, reportSpam,
updateContact, importContact, deleteContact, fetchProfilePhotos, fetchCommonChats, reportSpam,
} from './users';
export {

View File

@ -17,8 +17,7 @@ import {
import { buildApiUser, buildApiUserFromFull, buildApiUsersAndStatuses } from '../apiBuilders/users';
import { buildApiChatFromPreview } from '../apiBuilders/chats';
import { buildApiPhoto } from '../apiBuilders/common';
import localDb from '../localDb';
import { addEntitiesWithPhotosToLocalDb, addPhotoToLocalDb } from '../helpers';
import { addEntitiesWithPhotosToLocalDb, addPhotoToLocalDb, addUserToLocalDb } from '../helpers';
import { buildApiPeerId } from '../apiBuilders/peers';
let onUpdate: OnApiUpdate;
@ -115,7 +114,7 @@ export async function fetchContactList() {
result.users.forEach((user) => {
if (user instanceof GramJs.User) {
localDb.users[buildApiPeerId(user.id, 'user')] = user;
addUserToLocalDb(user, true);
}
});
@ -135,14 +134,14 @@ export async function fetchUsers({ users }: { users: ApiUser[] }) {
result.forEach((user) => {
if (user instanceof GramJs.User) {
localDb.users[buildApiPeerId(user.id, 'user')] = user;
addUserToLocalDb(user, true);
}
});
return buildApiUsersAndStatuses(result);
}
export function updateContact({
export async function importContact({
phone,
firstName,
lastName,
@ -151,33 +150,42 @@ export function updateContact({
firstName?: string;
lastName?: string;
}) {
return invokeRequest(new GramJs.contacts.ImportContacts({
const result = await invokeRequest(new GramJs.contacts.ImportContacts({
contacts: [buildInputContact({
phone: phone || '',
firstName: firstName || '',
lastName: lastName || '',
})],
}), true);
}));
if (result instanceof GramJs.contacts.ImportedContacts && result.users.length) {
addUserToLocalDb(result.users[0]);
}
return result?.imported.length ? buildApiPeerId(result.imported[0].userId, 'user') : undefined;
}
export function addContact({
export function updateContact({
id,
accessHash,
phoneNumber = '',
firstName = '',
lastName = '',
shouldSharePhoneNumber = false,
}: {
id: string;
accessHash?: string;
phoneNumber?: string;
firstName?: string;
lastName?: string;
shouldSharePhoneNumber?: boolean;
}) {
return invokeRequest(new GramJs.contacts.AddContact({
id: buildInputEntity(id, accessHash) as GramJs.InputUser,
firstName,
lastName,
phone: phoneNumber,
...(shouldSharePhoneNumber && { addPhonePrivacyException: shouldSharePhoneNumber }),
}), true);
}

View File

@ -5,6 +5,7 @@ export { default as Dialogs } from '../components/main/Dialogs';
export { default as Notifications } from '../components/main/Notifications';
export { default as SafeLinkModal } from '../components/main/SafeLinkModal';
export { default as HistoryCalendar } from '../components/main/HistoryCalendar';
export { default as NewContactModal } from '../components/main/NewContactModal';
export { default as CalendarModal } from '../components/common/CalendarModal';
export { default as DeleteMessageModal } from '../components/common/DeleteMessageModal';

View File

@ -10,11 +10,13 @@ import { throttle } from '../../../util/schedulers';
import { filterUsersByName, sortUserIds } from '../../../global/helpers';
import useInfiniteScroll from '../../../hooks/useInfiniteScroll';
import useHistoryBack from '../../../hooks/useHistoryBack';
import useLang from '../../../hooks/useLang';
import PrivateChatInfo from '../../common/PrivateChatInfo';
import InfiniteScroll from '../../ui/InfiniteScroll';
import ListItem from '../../ui/ListItem';
import Loading from '../../ui/Loading';
import FloatingActionButton from '../../ui/FloatingActionButton';
export type OwnProps = {
filter: string;
@ -43,8 +45,11 @@ const ContactList: FC<OwnProps & StateProps> = ({
const {
loadContactList,
openChat,
openNewContactDialog,
} = getActions();
const lang = useLang();
// Due to the parent Transition, this component never gets unmounted,
// that's why we use throttled API call on every update.
useEffect(() => {
@ -91,6 +96,13 @@ const ContactList: FC<OwnProps & StateProps> = ({
) : (
<Loading key="loading" />
)}
<FloatingActionButton
isShown
onClick={openNewContactDialog}
ariaLabel={lang('CreateNewContact')}
>
<i className="icon-add-user-filled" />
</FloatingActionButton>
</InfiniteScroll>
);
};

View File

@ -48,6 +48,7 @@ import HistoryCalendar from './HistoryCalendar.async';
import GroupCall from '../calls/group/GroupCall.async';
import ActiveCallHeader from '../calls/ActiveCallHeader.async';
import CallFallbackConfirm from '../calls/CallFallbackConfirm.async';
import NewContactModal from './NewContactModal.async';
import './Main.scss';
@ -73,6 +74,8 @@ type StateProps = {
wasTimeFormatSetManually?: boolean;
isCallFallbackConfirmOpen: boolean;
addedSetIds?: string[];
newContactUserId?: string;
newContactByPhoneNumber?: boolean;
};
const NOTIFICATION_INTERVAL = 1000;
@ -104,6 +107,8 @@ const Main: FC<StateProps> = ({
wasTimeFormatSetManually,
isCallFallbackConfirmOpen,
addedSetIds,
newContactUserId,
newContactByPhoneNumber,
}) => {
const {
sync,
@ -330,6 +335,11 @@ const Main: FC<StateProps> = ({
<ActiveCallHeader groupCallId={activeGroupCallId} />
</>
)}
<NewContactModal
isOpen={Boolean(newContactUserId || newContactByPhoneNumber)}
userId={newContactUserId}
isByPhoneNumber={newContactByPhoneNumber}
/>
<DownloadManager />
<CallFallbackConfirm isOpen={isCallFallbackConfirmOpen} />
<UnreadCount isForAppBadge />
@ -388,6 +398,8 @@ export default memo(withGlobal(
wasTimeFormatSetManually,
isCallFallbackConfirmOpen: Boolean(global.groupCalls.isFallbackConfirmOpen),
addedSetIds: global.stickers.added.setIds,
newContactUserId: global.newContact?.userId,
newContactByPhoneNumber: global.newContact?.isByPhoneNumber,
};
},
)(Main));

View File

@ -0,0 +1,16 @@
import React, { FC, memo } from '../../lib/teact/teact';
import { Bundles } from '../../util/moduleLoader';
import { OwnProps } from './NewContactModal';
import useModuleLoader from '../../hooks/useModuleLoader';
const NewContactModalAsync: FC<OwnProps> = (props) => {
const { isOpen } = props;
const NewContactModal = useModuleLoader(Bundles.Extra, 'NewContactModal', !isOpen);
// eslint-disable-next-line react/jsx-props-no-spreading
return NewContactModal ? <NewContactModal {...props} /> : undefined;
};
export default memo(NewContactModalAsync);

View File

@ -0,0 +1,38 @@
.NewContactModal {
.modal-dialog {
max-width: 28rem;
}
&__new-contact {
display: flex;
&-fieldset {
flex: 1;
margin-inline-start: 1rem;
}
}
&__profile {
display: flex;
align-items: center;
margin-bottom: 2rem;
&-info {
margin-inline-start: 1rem;
}
}
&__user-status {
color: var(--color-text-secondary);
}
&__phone-number {
font-size: 1.5rem;
margin-bottom: 0;
}
&__help-text {
font-size: 0.9375rem;
color: var(--color-text-secondary);
}
}

View File

@ -0,0 +1,235 @@
import React, {
FC, memo, useCallback, useEffect, useRef, useState,
} from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global';
import { ApiCountryCode, ApiUser, ApiUserStatus } from '../../api/types';
import { IS_TOUCH_ENV } from '../../util/environment';
import { getUserStatus } from '../../global/helpers';
import { selectUser, selectUserStatus } from '../../global/selectors';
import renderText from '../common/helpers/renderText';
import { formatPhoneNumberWithCode } from '../../util/phoneNumber';
import useLang from '../../hooks/useLang';
import useFlag from '../../hooks/useFlag';
import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';
import Modal from '../ui/Modal';
import Avatar from '../common/Avatar';
import InputText from '../ui/InputText';
import Checkbox from '../ui/Checkbox';
import Button from '../ui/Button';
import './NewContactModal.scss';
const ANIMATION_DURATION = 200;
export type OwnProps = {
isOpen: boolean;
userId?: string;
isByPhoneNumber?: boolean;
};
type StateProps = {
user?: ApiUser;
userStatus?: ApiUserStatus;
phoneCodeList: ApiCountryCode[];
serverTimeOffset?: number;
};
const NewContactModal: FC<OwnProps & StateProps> = ({
isOpen,
userId,
isByPhoneNumber,
user,
userStatus,
phoneCodeList,
serverTimeOffset,
}) => {
const { updateContact, importContact, closeNewContactDialog } = getActions();
const lang = useLang();
const renderingUser = useCurrentOrPrev(user);
const renderingIsByPhoneNumber = useCurrentOrPrev(isByPhoneNumber);
// eslint-disable-next-line no-null/no-null
const inputRef = useRef<HTMLInputElement>(null);
const [isShown, markIsShown, unmarkIsShown] = useFlag();
const [firstName, setFirstName] = useState<string>(renderingUser?.firstName ?? '');
const [lastName, setLastName] = useState<string>(renderingUser?.lastName ?? '');
const [phone, setPhone] = useState<string>(renderingUser?.phoneNumber ?? '');
const [shouldSharePhoneNumber, setShouldSharePhoneNumber] = useState<boolean>(true);
const canBeSubmitted = Boolean(firstName && (!isByPhoneNumber || phone));
useEffect(() => {
if (isOpen) {
markIsShown();
setFirstName(renderingUser?.firstName ?? '');
setLastName(renderingUser?.lastName ?? '');
setPhone(renderingUser?.phoneNumber ?? '');
setShouldSharePhoneNumber(true);
}
}, [isOpen, markIsShown, renderingUser?.firstName, renderingUser?.lastName, renderingUser?.phoneNumber]);
useEffect(() => {
if (!IS_TOUCH_ENV && isShown) {
setTimeout(() => { inputRef.current?.focus(); }, ANIMATION_DURATION);
}
}, [isShown]);
const handleFirstNameChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
setFirstName(e.target.value);
}, []);
const handlePhoneChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
setPhone(formatPhoneNumberWithCode(phoneCodeList, e.target.value));
}, [phoneCodeList]);
const handleLastNameChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
setLastName(e.target.value);
}, []);
const handleClose = useCallback(() => {
closeNewContactDialog();
setFirstName('');
setLastName('');
setPhone('');
}, [closeNewContactDialog]);
const handleSubmit = useCallback(() => {
if (isByPhoneNumber || !userId) {
importContact({
firstName,
lastName,
phoneNumber: phone,
});
} else {
updateContact({
userId,
firstName,
lastName,
shouldSharePhoneNumber,
});
}
}, [firstName, importContact, isByPhoneNumber, lastName, phone, shouldSharePhoneNumber, updateContact, userId]);
if (!isOpen && !isShown) {
return undefined;
}
function renderAddContact() {
return (
<>
<div className="NewContactModal__profile" dir={lang.isRtl ? 'rtl' : undefined}>
<Avatar size="jumbo" user={renderingUser} text={`${firstName} ${lastName}`} />
<div className="NewContactModal__profile-info">
<p className="NewContactModal__phone-number">
{renderingUser?.phoneNumber
? formatPhoneNumberWithCode(phoneCodeList, renderingUser.phoneNumber)
: lang('MobileHidden')}
</p>
<span className="NewContactModal__user-status" dir="auto">
{getUserStatus(lang, renderingUser!, userStatus, serverTimeOffset!)}
</span>
</div>
</div>
<InputText
ref={inputRef}
value={firstName}
label={lang('FirstName')}
tabIndex={0}
onChange={handleFirstNameChange}
/>
<InputText
value={lastName}
label={lang('LastName')}
tabIndex={0}
onChange={handleLastNameChange}
/>
<p className="NewContactModal__help-text">
{renderText(lang('NewContact.Phone.Hidden.Text', renderingUser?.firstName), ['emoji', 'simple_markdown'])}
</p>
<Checkbox
checked={shouldSharePhoneNumber}
tabIndex={0}
onCheck={setShouldSharePhoneNumber}
label={lang('lng_new_contact_share')}
/>
<p className="NewContactModal__help-text">
{renderText(lang('AddContact.SharedContactExceptionInfo', renderingUser?.firstName))}
</p>
</>
);
}
function renderCreateContact() {
return (
<div className="NewContactModal__new-contact" dir={lang.isRtl ? 'rtl' : undefined}>
<Avatar size="jumbo" text={`${firstName} ${lastName}`} />
<div className="NewContactModal__new-contact-fieldset">
<InputText
ref={inputRef}
value={phone}
inputMode="tel"
label={lang('lng_contact_phone')}
tabIndex={0}
onChange={handlePhoneChange}
/>
<InputText
value={firstName}
label={lang('FirstName')}
tabIndex={0}
onChange={handleFirstNameChange}
/>
<InputText
value={lastName}
label={lang('LastName')}
tabIndex={0}
onChange={handleLastNameChange}
/>
</div>
</div>
);
}
return (
<Modal
className="NewContactModal"
title={lang('NewContact')}
isOpen={isOpen}
onClose={handleClose}
onCloseAnimationEnd={unmarkIsShown}
>
{renderingUser && renderAddContact()}
{renderingIsByPhoneNumber && renderCreateContact()}
<div className="dialog-buttons">
<Button
isText
className="confirm-dialog-button"
onClick={handleClose}
>
{lang('Cancel')}
</Button>
<Button
isText
className="confirm-dialog-button"
disabled={!canBeSubmitted}
onClick={handleSubmit}
>
{lang('Done')}
</Button>
</div>
</Modal>
);
};
export default memo(withGlobal<OwnProps>(
(global, { userId }): StateProps => {
return {
user: userId ? selectUser(global, userId) : undefined,
userStatus: userId ? selectUserStatus(global, userId) : undefined,
serverTimeOffset: global.serverTimeOffset,
phoneCodeList: global.countryList.phoneCodes,
};
},
)(NewContactModal));

View File

@ -35,7 +35,7 @@ const ChatReportPanel: FC<OwnProps & StateProps> = ({
chatId, className, chat, user, settings, currentUserId,
}) => {
const {
addContact,
openAddContactDialog,
blockContact,
reportSpam,
deleteChat,
@ -57,11 +57,11 @@ const ChatReportPanel: FC<OwnProps & StateProps> = ({
const isBasicGroup = chat && isChatBasicGroup(chat);
const handleAddContact = useCallback(() => {
addContact({ chatId });
openAddContactDialog({ userId: chatId });
if (isAutoArchived) {
toggleChatArchived({ chatId });
}
}, [addContact, isAutoArchived, toggleChatArchived, chatId]);
}, [openAddContactDialog, isAutoArchived, toggleChatArchived, chatId]);
const handleConfirmBlock = useCallback(() => {
closeBlockUserModal();
@ -103,7 +103,6 @@ const ChatReportPanel: FC<OwnProps & StateProps> = ({
{canAddContact && (
<Button
isText
ripple
fluid
size="tiny"
className="UserReportPanel--Button"

View File

@ -91,7 +91,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
joinGroupCall,
createGroupCall,
openLinkedChat,
addContact,
openAddContactDialog,
openCallFallbackConfirm,
toggleStatistics,
} = getActions();
@ -150,9 +150,9 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
}, [chatId, closeMenu, openLinkedChat]);
const handleAddContactClick = useCallback(() => {
addContact({ userId: chatId });
openAddContactDialog({ userId: chatId });
closeMenu();
}, [addContact, chatId, closeMenu]);
}, [openAddContactDialog, chatId, closeMenu]);
const handleSubscribe = useCallback(() => {
onSubscribeChannel();

View File

@ -133,7 +133,7 @@ const RightHeader: FC<OwnProps & StateProps> = ({
searchTextMessagesLocal,
toggleManagement,
openHistoryCalendar,
addContact,
openAddContactDialog,
toggleStatistics,
setEditingExportedInvite,
deleteExportedChatInvite,
@ -171,8 +171,8 @@ const RightHeader: FC<OwnProps & StateProps> = ({
}, [setGifSearchQuery]);
const handleAddContact = useCallback(() => {
addContact({ userId });
}, [addContact, userId]);
openAddContactDialog({ userId });
}, [openAddContactDialog, userId]);
const [shouldSkipTransition, setShouldSkipTransition] = useState(!isColumnOpen);

View File

@ -17,6 +17,7 @@ type OwnProps = {
subLabel?: string;
checked: boolean;
disabled?: boolean;
tabIndex?: number;
round?: boolean;
blocking?: boolean;
isLoading?: boolean;
@ -32,6 +33,7 @@ const Checkbox: FC<OwnProps> = ({
label,
subLabel,
checked,
tabIndex,
disabled,
round,
blocking,
@ -67,6 +69,7 @@ const Checkbox: FC<OwnProps> = ({
value={value}
checked={checked}
disabled={disabled}
tabIndex={tabIndex}
onChange={handleChange}
/>
<div className="Checkbox-main">

View File

@ -19,6 +19,7 @@ type OwnProps = {
placeholder?: string;
autoComplete?: string;
maxLength?: number;
tabIndex?: number;
inputMode?: 'text' | 'none' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search';
onChange?: (e: ChangeEvent<HTMLInputElement>) => void;
onInput?: (e: FormEvent<HTMLInputElement>) => void;
@ -42,6 +43,7 @@ const InputText: FC<OwnProps> = ({
autoComplete,
inputMode,
maxLength,
tabIndex,
onChange,
onInput,
onKeyPress,
@ -70,6 +72,7 @@ const InputText: FC<OwnProps> = ({
id={id}
dir="auto"
value={value || ''}
tabIndex={tabIndex}
placeholder={placeholder}
maxLength={maxLength}
autoComplete={autoComplete}

View File

@ -6,15 +6,16 @@ import { ApiUser } from '../../../api/types';
import { ManagementProgress } from '../../../types';
import { debounce, throttle } from '../../../util/schedulers';
import { buildCollectionByKey, pick, unique } from '../../../util/iteratees';
import { buildCollectionByKey, unique } from '../../../util/iteratees';
import { isUserBot, isUserId } from '../../helpers';
import { callApi } from '../../../api/gramjs';
import { selectChat, selectCurrentMessageList, selectUser } from '../../selectors';
import {
addChats, addUsers, replaceUserStatuses, updateChat, updateManagementProgress, updateUser, updateUsers,
updateUserSearch, updateUserSearchFetchingStatus,
addChats, addUsers, closeNewContactDialog, replaceUserStatuses, updateChat, updateManagementProgress, updateUser,
updateUsers, updateUserSearch, updateUserSearchFetchingStatus,
} from '../../reducers';
import { getServerTime } from '../../../util/serverTime';
import * as langProvider from '../../../util/langProvider';
const runDebouncedForFetchFullUser = debounce((cb) => cb(), 500, false, true);
const TOP_PEERS_REQUEST_COOLDOWN = 60; // 1 min
@ -105,10 +106,10 @@ addActionHandler('loadCommonChats', async (global) => {
addActionHandler('updateContact', (global, actions, payload) => {
const {
userId, isMuted, firstName, lastName,
} = payload!;
userId, isMuted = false, firstName, lastName, shouldSharePhoneNumber,
} = payload;
void updateContact(userId, isMuted, firstName, lastName);
void updateContact(userId, isMuted, firstName, lastName, shouldSharePhoneNumber);
});
addActionHandler('deleteContact', (global, actions, payload) => {
@ -168,8 +169,9 @@ async function updateContact(
isMuted: boolean,
firstName: string,
lastName?: string,
shouldSharePhoneNumber?: boolean,
) {
const global = getGlobal();
let global = getGlobal();
const user = selectUser(global, userId);
if (!user) {
return;
@ -180,22 +182,24 @@ async function updateContact(
setGlobal(updateManagementProgress(getGlobal(), ManagementProgress.InProgress));
let result;
if (user.phoneNumber) {
result = await callApi('updateContact', { phone: user.phoneNumber, firstName, lastName });
if (!user.isContact && user.phoneNumber) {
result = await callApi('importContact', { phone: user.phoneNumber, firstName, lastName });
} else {
const { id, accessHash } = user;
result = await callApi('addContact', {
result = await callApi('updateContact', {
id,
accessHash,
phoneNumber: '',
firstName,
lastName,
shouldSharePhoneNumber,
});
}
global = getGlobal();
if (result) {
setGlobal(updateUser(
getGlobal(),
global,
user.id,
{
firstName,
@ -204,7 +208,9 @@ async function updateContact(
));
}
setGlobal(updateManagementProgress(getGlobal(), ManagementProgress.Complete));
global = updateManagementProgress(global, ManagementProgress.Complete);
global = closeNewContactDialog(global);
setGlobal(global);
}
async function deleteContact(userId: string) {
@ -257,14 +263,22 @@ addActionHandler('setUserSearchQuery', (global, actions, payload) => {
});
});
addActionHandler('addContact', (global, actions, payload) => {
const { userId } = payload!;
const user = selectUser(global, userId);
if (!user) {
return;
addActionHandler('importContact', async (global, actions, payload) => {
const { phoneNumber: phone, firstName, lastName } = payload!;
const result = await callApi('importContact', { phone, firstName, lastName });
if (result) {
actions.openChat({ id: result });
return closeNewContactDialog(getGlobal());
}
void callApi('addContact', pick(user, ['id', 'accessHash', 'firstName', 'lastName', 'phoneNumber']));
actions.showNotification({
message: langProvider.getTranslation('Contacts.PhoneNumber.NotRegistred'),
});
return undefined;
});
addActionHandler('reportSpam', (global, actions, payload) => {

View File

@ -1,6 +1,6 @@
import { addActionHandler } from '../../index';
import { updateUserSearch } from '../../reducers';
import { closeNewContactDialog, updateUserSearch } from '../../reducers';
addActionHandler('setUserSearchQuery', (global, actions, payload) => {
const { query } = payload!;
@ -12,3 +12,25 @@ addActionHandler('setUserSearchQuery', (global, actions, payload) => {
query,
});
});
addActionHandler('openAddContactDialog', (global, actions, payload) => {
const { userId } = payload!;
return {
...global,
newContact: { userId },
};
});
addActionHandler('openNewContactDialog', (global) => {
return {
...global,
newContact: {
isByPhoneNumber: true,
},
};
});
addActionHandler('closeNewContactDialog', (global) => {
return closeNewContactDialog(global);
});

View File

@ -210,3 +210,10 @@ export function addUserStatuses(global: GlobalState, newById: Record<string, Api
return global;
}
export function closeNewContactDialog(global: GlobalState): GlobalState {
return {
...global,
newContact: undefined,
};
}

View File

@ -510,6 +510,11 @@ export type GlobalState = {
statistics: {
byChatId: Record<string, ApiStatistics>;
};
newContact?: {
userId?: string;
isByPhoneNumber?: boolean;
};
};
export interface ActionPayloads {
@ -578,6 +583,25 @@ export interface ActionPayloads {
setAudioPlayerOrigin: {
origin: AudioOrigin;
};
// Users
openAddContactDialog: {
userId?: string;
};
openNewContactDialog: undefined;
closeNewContactDialog: undefined;
importContact: {
phoneNumber: string;
firstName: string;
lastName?: string;
};
updateContact: {
userId: string;
firstName: string;
lastName?: string;
isMuted?: boolean;
shouldSharePhoneNumber?: boolean;
};
}
export type NonTypedActionNames = (
@ -641,7 +665,7 @@ export type NonTypedActionNames = (
'acceptInviteConfirmation' |
// users
'loadFullUser' | 'loadNearestCountry' | 'loadTopUsers' | 'loadContactList' |
'loadCurrentUser' | 'updateProfile' | 'checkUsername' | 'addContact' | 'updateContact' |
'loadCurrentUser' | 'updateProfile' | 'checkUsername' |
'deleteContact' | 'loadUser' | 'setUserSearchQuery' | 'loadCommonChats' | 'reportSpam' |
// chat creation
'createChannel' | 'createGroupChat' | 'resetChatCreation' |