New Contact: Allow to rename and add phone number (#1777)
This commit is contained in:
parent
1c50550079
commit
6d063c2132
@ -2,6 +2,9 @@
|
||||
"extends": [
|
||||
"stylelint-config-recommended-scss"
|
||||
],
|
||||
"ignoreFiles": [
|
||||
"dist/*.css"
|
||||
],
|
||||
"plugins": [
|
||||
"stylelint-declaration-block-no-ignored-properties",
|
||||
"stylelint-high-performance-animation",
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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));
|
||||
|
||||
16
src/components/main/NewContactModal.async.tsx
Normal file
16
src/components/main/NewContactModal.async.tsx
Normal 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);
|
||||
38
src/components/main/NewContactModal.scss
Normal file
38
src/components/main/NewContactModal.scss
Normal 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);
|
||||
}
|
||||
}
|
||||
235
src/components/main/NewContactModal.tsx
Normal file
235
src/components/main/NewContactModal.tsx
Normal 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));
|
||||
@ -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"
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -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);
|
||||
});
|
||||
|
||||
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@ -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' |
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user