Show confirmation modal before joining with invite links (#1225)

This commit is contained in:
Alexander Zinchuk 2021-07-03 15:34:03 +03:00
parent 676e914597
commit 7c860f27b3
21 changed files with 232 additions and 124 deletions

View File

@ -907,12 +907,15 @@ export async function openChatByInvite(hash: string) {
let chat: ApiChat | undefined; let chat: ApiChat | undefined;
if (result instanceof GramJs.ChatInvite) { if (result instanceof GramJs.ChatInvite) {
const updates = await invokeRequest(new GramJs.messages.ImportChatInvite({ hash }), true); onUpdate({
if (!(updates instanceof GramJs.Updates) || !updates.chats.length) { '@type': 'showInvite',
return undefined; data: {
} title: result.title,
hash,
chat = buildApiChatFromPreview(updates.chats[0]); participantsCount: result.participantsCount,
isChannel: result.channel,
},
});
} else { } else {
chat = buildApiChatFromPreview(result.chat); chat = buildApiChatFromPreview(result.chat);
@ -978,3 +981,14 @@ function updateLocalDb(result: (
}); });
} }
} }
export async function importChatInvite({ hash }: {hash: string}) {
const updates = await invokeRequest(new GramJs.messages.ImportChatInvite({ hash }), true);
if (!(updates instanceof GramJs.Updates) || !updates.chats.length) {
return undefined;
}
const chat = buildApiChatFromPreview(updates.chats[0]);
return chat;
}

View File

@ -14,7 +14,7 @@ export {
fetchChatFolders, editChatFolder, deleteChatFolder, fetchRecommendedChatFolders, fetchChatFolders, editChatFolder, deleteChatFolder, fetchRecommendedChatFolders,
getChatByUsername, togglePreHistoryHidden, updateChatDefaultBannedRights, updateChatMemberBannedRights, getChatByUsername, togglePreHistoryHidden, updateChatDefaultBannedRights, updateChatMemberBannedRights,
updateChatTitle, updateChatAbout, toggleSignatures, updateChatAdmin, fetchGroupsForDiscussion, setDiscussionGroup, updateChatTitle, updateChatAbout, toggleSignatures, updateChatAdmin, fetchGroupsForDiscussion, setDiscussionGroup,
migrateChat, openChatByInvite, fetchMembers, migrateChat, openChatByInvite, fetchMembers, importChatInvite,
} from './chats'; } from './chats';
export { export {

View File

@ -74,6 +74,11 @@ export type ApiUpdateChatJoin = {
id: number; id: number;
}; };
export type ApiUpdateShowInvite = {
'@type': 'showInvite';
data: ApiInviteInfo;
};
export type ApiUpdateChatLeave = { export type ApiUpdateChatLeave = {
'@type': 'updateChatLeave'; '@type': 'updateChatLeave';
id: number; id: number;
@ -311,6 +316,13 @@ export type ApiError = {
textParams?: Record<string, string>; textParams?: Record<string, string>;
}; };
export type ApiInviteInfo = {
title: string;
hash: string;
isChannel?: boolean;
participantsCount?: number;
};
export type ApiUpdateError = { export type ApiUpdateError = {
'@type': 'error'; '@type': 'error';
error: ApiError; error: ApiError;
@ -395,7 +407,7 @@ export type ApiUpdate = (
ApiUpdateDeleteScheduledMessages | ApiUpdateResetMessages | ApiUpdateDeleteScheduledMessages | ApiUpdateResetMessages |
ApiUpdateTwoFaError | updateTwoFaStateWaitCode | ApiUpdateTwoFaError | updateTwoFaStateWaitCode |
ApiUpdateNotifySettings | ApiUpdateNotifyExceptions | ApiUpdatePeerBlocked | ApiUpdatePrivacy | ApiUpdateNotifySettings | ApiUpdateNotifyExceptions | ApiUpdatePeerBlocked | ApiUpdatePrivacy |
ApiUpdateServerTimeOffset ApiUpdateServerTimeOffset | ApiUpdateShowInvite
); );
export type OnApiUpdate = (update: ApiUpdate) => void; export type OnApiUpdate = (update: ApiUpdate) => void;

View File

@ -1,7 +1,7 @@
export { default as MediaViewer } from '../components/mediaViewer/MediaViewer'; export { default as MediaViewer } from '../components/mediaViewer/MediaViewer';
export { default as ForwardPicker } from '../components/main/ForwardPicker'; export { default as ForwardPicker } from '../components/main/ForwardPicker';
export { default as Errors } from '../components/main/Errors'; export { default as Dialogs } from '../components/main/Dialogs';
export { default as Notifications } from '../components/main/Notifications'; export { default as Notifications } from '../components/main/Notifications';
export { default as SafeLinkModal } from '../components/main/SafeLinkModal'; export { default as SafeLinkModal } from '../components/main/SafeLinkModal';
export { default as HistoryCalendar } from '../components/main/HistoryCalendar'; export { default as HistoryCalendar } from '../components/main/HistoryCalendar';

View File

@ -35,7 +35,7 @@ type StateProps = {
notifyExceptions?: Record<number, NotifyException>; notifyExceptions?: Record<number, NotifyException>;
}; };
type DispatchProps = Pick<GlobalActions, 'loadRecommendedChatFolders' | 'addChatFolder' | 'showError'>; type DispatchProps = Pick<GlobalActions, 'loadRecommendedChatFolders' | 'addChatFolder' | 'showDialog'>;
const runThrottledForLoadRecommended = throttle((cb) => cb(), 60000, true); const runThrottledForLoadRecommended = throttle((cb) => cb(), 60000, true);
@ -53,7 +53,7 @@ const SettingsFoldersMain: FC<OwnProps & StateProps & DispatchProps> = ({
notifyExceptions, notifyExceptions,
loadRecommendedChatFolders, loadRecommendedChatFolders,
addChatFolder, addChatFolder,
showError, showDialog,
}) => { }) => {
const [animationData, setAnimationData] = useState<Record<string, any>>(); const [animationData, setAnimationData] = useState<Record<string, any>>();
const [isAnimationLoaded, setIsAnimationLoaded] = useState(false); const [isAnimationLoaded, setIsAnimationLoaded] = useState(false);
@ -75,8 +75,8 @@ const SettingsFoldersMain: FC<OwnProps & StateProps & DispatchProps> = ({
const handleCreateFolder = useCallback(() => { const handleCreateFolder = useCallback(() => {
if (Object.keys(foldersById).length >= MAX_ALLOWED_FOLDERS) { if (Object.keys(foldersById).length >= MAX_ALLOWED_FOLDERS) {
showError({ showDialog({
error: { data: {
message: 'DIALOG_FILTERS_TOO_MUCH', message: 'DIALOG_FILTERS_TOO_MUCH',
}, },
}); });
@ -85,7 +85,7 @@ const SettingsFoldersMain: FC<OwnProps & StateProps & DispatchProps> = ({
} }
onCreateFolder(); onCreateFolder();
}, [foldersById, showError, onCreateFolder]); }, [foldersById, showDialog, onCreateFolder]);
const lang = useLang(); const lang = useLang();
@ -111,8 +111,8 @@ const SettingsFoldersMain: FC<OwnProps & StateProps & DispatchProps> = ({
const handleCreateFolderFromRecommended = useCallback((folder: ApiChatFolder) => { const handleCreateFolderFromRecommended = useCallback((folder: ApiChatFolder) => {
if (Object.keys(foldersById).length >= MAX_ALLOWED_FOLDERS) { if (Object.keys(foldersById).length >= MAX_ALLOWED_FOLDERS) {
showError({ showDialog({
error: { data: {
message: 'DIALOG_FILTERS_TOO_MUCH', message: 'DIALOG_FILTERS_TOO_MUCH',
}, },
}); });
@ -121,7 +121,7 @@ const SettingsFoldersMain: FC<OwnProps & StateProps & DispatchProps> = ({
} }
addChatFolder({ folder }); addChatFolder({ folder });
}, [foldersById, addChatFolder, showError]); }, [foldersById, addChatFolder, showDialog]);
return ( return (
<div className="settings-content custom-scroll"> <div className="settings-content custom-scroll">
@ -238,5 +238,5 @@ export default memo(withGlobal<OwnProps>(
notifyExceptions: selectNotifyExceptions(global), notifyExceptions: selectNotifyExceptions(global),
}; };
}, },
(setGlobal, actions): DispatchProps => pick(actions, ['loadRecommendedChatFolders', 'addChatFolder', 'showError']), (setGlobal, actions): DispatchProps => pick(actions, ['loadRecommendedChatFolders', 'addChatFolder', 'showDialog']),
)(SettingsFoldersMain)); )(SettingsFoldersMain));

View File

@ -3,11 +3,11 @@ import { Bundles } from '../../util/moduleLoader';
import useModuleLoader from '../../hooks/useModuleLoader'; import useModuleLoader from '../../hooks/useModuleLoader';
const ErrorsAsync: FC = ({ isOpen }) => { const DialogsAsync: FC = ({ isOpen }) => {
const Errors = useModuleLoader(Bundles.Extra, 'Errors', !isOpen); const Dialogs = useModuleLoader(Bundles.Extra, 'Dialogs', !isOpen);
// eslint-disable-next-line react/jsx-props-no-spreading // eslint-disable-next-line react/jsx-props-no-spreading
return Errors ? <Errors /> : undefined; return Dialogs ? <Dialogs /> : undefined;
}; };
export default memo(ErrorsAsync); export default memo(DialogsAsync);

View File

@ -1,4 +1,4 @@
#Errors { #Dialogs {
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
@ -6,3 +6,11 @@
height: 100vh; height: 100vh;
z-index: var(--z-modal); z-index: var(--z-modal);
} }
.buttons {
display: flex;
button {
flex: 1;
}
}

View File

@ -0,0 +1,103 @@
import React, { FC, memo } from '../../lib/teact/teact';
import { withGlobal } from '../../lib/teact/teactn';
import { GlobalActions } from '../../global/types';
import { ApiError, ApiInviteInfo } from '../../api/types';
import getReadableErrorText from '../../util/getReadableErrorText';
import { pick } from '../../util/iteratees';
import useLang from '../../hooks/useLang';
import Modal from '../ui/Modal';
import Button from '../ui/Button';
import './Dialogs.scss';
type StateProps = {
dialogs: (ApiError | ApiInviteInfo)[];
};
type DispatchProps = Pick<GlobalActions, 'dismissDialog' | 'acceptInviteConfirmation'>;
const Dialogs: FC<StateProps & DispatchProps> = ({ dialogs, dismissDialog, acceptInviteConfirmation }) => {
const lang = useLang();
if (!dialogs.length) {
return undefined;
}
const renderInvite = (invite: ApiInviteInfo) => {
const {
hash, title, participantsCount, isChannel,
} = invite;
const handleJoinClick = () => {
acceptInviteConfirmation({
hash,
});
dismissDialog();
};
const participantsText = isChannel
? lang('Subscribers', participantsCount, 'i')
: lang('Members', participantsCount, 'i');
const joinText = isChannel ? lang('ChannelJoin') : lang('JoinGroup');
return (
<Modal
isOpen
onClose={dismissDialog}
className="error"
title={title}
>
{participantsCount !== undefined && <p>{participantsText}</p>}
<Button isText className="confirm-dialog-button" onClick={handleJoinClick}>{joinText}</Button>
<Button isText className="confirm-dialog-button" onClick={dismissDialog}>{lang('Cancel')}</Button>
</Modal>
);
};
const renderError = (error: ApiError) => {
return (
<Modal
isOpen
onClose={dismissDialog}
className="error"
title={getErrorHeader(error)}
>
<p>{getReadableErrorText(error)}</p>
<div className="buttons">
<Button isText onClick={dismissDialog}>{lang('OK')}</Button>
</div>
</Modal>
);
};
const renderDialog = (dialog: ApiError | ApiInviteInfo) => {
if ('hash' in dialog) {
return renderInvite(dialog);
}
return renderError(dialog);
};
return (
<div id="Dialogs">
{dialogs.map(renderDialog)}
</div>
);
};
function getErrorHeader(error: ApiError) {
if (error.isSlowMode) {
return 'Slowmode enabled';
}
return 'Something went wrong';
}
export default memo(withGlobal(
(global): StateProps => pick(global, ['dialogs']),
(setGlobal, actions): DispatchProps => pick(actions, ['dismissDialog', 'acceptInviteConfirmation']),
)(Dialogs));

View File

@ -1,57 +0,0 @@
import React, { FC, memo } from '../../lib/teact/teact';
import { withGlobal } from '../../lib/teact/teactn';
import { GlobalActions } from '../../global/types';
import { ApiError } from '../../api/types';
import getReadableErrorText from '../../util/getReadableErrorText';
import { pick } from '../../util/iteratees';
import useLang from '../../hooks/useLang';
import Modal from '../ui/Modal';
import Button from '../ui/Button';
import './Errors.scss';
type StateProps = {
errors: ApiError[];
};
type DispatchProps = Pick<GlobalActions, 'dismissError'>;
const Errors: FC<StateProps & DispatchProps> = ({ errors, dismissError }) => {
const lang = useLang();
if (!errors.length) {
return undefined;
}
return (
<div id="Errors">
{errors.map((error) => (
<Modal
isOpen
onClose={dismissError}
className="error"
title={getErrorHeader(error)}
>
<p>{getReadableErrorText(error)}</p>
<Button isText onClick={dismissError}>{lang('OK')}</Button>
</Modal>
))}
</div>
);
};
function getErrorHeader(error: ApiError) {
if (error.isSlowMode) {
return 'Slowmode enabled';
}
return 'Something went wrong';
}
export default memo(withGlobal(
(global): StateProps => pick(global, ['errors']),
(setGlobal, actions): DispatchProps => pick(actions, ['dismissError']),
)(Errors));

View File

@ -30,7 +30,7 @@ import RightColumn from '../right/RightColumn';
import MediaViewer from '../mediaViewer/MediaViewer.async'; import MediaViewer from '../mediaViewer/MediaViewer.async';
import AudioPlayer from '../middle/AudioPlayer'; import AudioPlayer from '../middle/AudioPlayer';
import Notifications from './Notifications.async'; import Notifications from './Notifications.async';
import Errors from './Errors.async'; import Dialogs from './Dialogs.async';
import ForwardPicker from './ForwardPicker.async'; import ForwardPicker from './ForwardPicker.async';
import SafeLinkModal from './SafeLinkModal.async'; import SafeLinkModal from './SafeLinkModal.async';
import HistoryCalendar from './HistoryCalendar.async'; import HistoryCalendar from './HistoryCalendar.async';
@ -45,7 +45,7 @@ type StateProps = {
isMediaViewerOpen: boolean; isMediaViewerOpen: boolean;
isForwardModalOpen: boolean; isForwardModalOpen: boolean;
hasNotifications: boolean; hasNotifications: boolean;
hasErrors: boolean; hasDialogs: boolean;
audioMessage?: ApiMessage; audioMessage?: ApiMessage;
safeLinkModalUrl?: string; safeLinkModalUrl?: string;
isHistoryCalendarOpen: boolean; isHistoryCalendarOpen: boolean;
@ -71,7 +71,7 @@ const Main: FC<StateProps & DispatchProps> = ({
isForwardModalOpen, isForwardModalOpen,
animationLevel, animationLevel,
hasNotifications, hasNotifications,
hasErrors, hasDialogs,
audioMessage, audioMessage,
safeLinkModalUrl, safeLinkModalUrl,
isHistoryCalendarOpen, isHistoryCalendarOpen,
@ -192,7 +192,7 @@ const Main: FC<StateProps & DispatchProps> = ({
<MediaViewer isOpen={isMediaViewerOpen} /> <MediaViewer isOpen={isMediaViewerOpen} />
<ForwardPicker isOpen={isForwardModalOpen} /> <ForwardPicker isOpen={isForwardModalOpen} />
<Notifications isOpen={hasNotifications} /> <Notifications isOpen={hasNotifications} />
<Errors isOpen={hasErrors} /> <Dialogs isOpen={hasDialogs} />
{audioMessage && <AudioPlayer key={audioMessage.id} message={audioMessage} noUi />} {audioMessage && <AudioPlayer key={audioMessage.id} message={audioMessage} noUi />}
<SafeLinkModal url={safeLinkModalUrl} /> <SafeLinkModal url={safeLinkModalUrl} />
<HistoryCalendar isOpen={isHistoryCalendarOpen} /> <HistoryCalendar isOpen={isHistoryCalendarOpen} />
@ -228,7 +228,7 @@ export default memo(withGlobal(
isMediaViewerOpen: selectIsMediaViewerOpen(global), isMediaViewerOpen: selectIsMediaViewerOpen(global),
isForwardModalOpen: selectIsForwardModalOpen(global), isForwardModalOpen: selectIsForwardModalOpen(global),
hasNotifications: Boolean(global.notifications.length), hasNotifications: Boolean(global.notifications.length),
hasErrors: Boolean(global.errors.length), hasDialogs: Boolean(global.dialogs.length),
audioMessage, audioMessage,
safeLinkModalUrl: global.safeLinkModalUrl, safeLinkModalUrl: global.safeLinkModalUrl,
isHistoryCalendarOpen: Boolean(global.historyCalendarSelectedAt), isHistoryCalendarOpen: Boolean(global.historyCalendarSelectedAt),

View File

@ -128,7 +128,7 @@ type StateProps = {
type DispatchProps = Pick<GlobalActions, ( type DispatchProps = Pick<GlobalActions, (
'sendMessage' | 'editMessage' | 'saveDraft' | 'forwardMessages' | 'sendMessage' | 'editMessage' | 'saveDraft' | 'forwardMessages' |
'clearDraft' | 'showError' | 'setStickerSearchQuery' | 'setGifSearchQuery' | 'clearDraft' | 'showDialog' | 'setStickerSearchQuery' | 'setGifSearchQuery' |
'openPollModal' | 'closePollModal' | 'loadScheduledHistory' | 'openChat' | 'closePaymentModal' | 'openPollModal' | 'closePollModal' | 'loadScheduledHistory' | 'openChat' | 'closePaymentModal' |
'clearReceipt' | 'addRecentEmoji' | 'loadEmojiKeywords' 'clearReceipt' | 'addRecentEmoji' | 'loadEmojiKeywords'
)>; )>;
@ -189,7 +189,7 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
editMessage, editMessage,
saveDraft, saveDraft,
clearDraft, clearDraft,
showError, showDialog,
setStickerSearchQuery, setStickerSearchQuery,
setGifSearchQuery, setGifSearchQuery,
forwardMessages, forwardMessages,
@ -428,8 +428,8 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
if (currentAttachments.length && text && text.length > CAPTION_MAX_LENGTH) { if (currentAttachments.length && text && text.length > CAPTION_MAX_LENGTH) {
const extraLength = text.length - CAPTION_MAX_LENGTH; const extraLength = text.length - CAPTION_MAX_LENGTH;
showError({ showDialog({
error: { data: {
message: 'CAPTION_TOO_LONG_PLEASE_REMOVE_CHARACTERS', message: 'CAPTION_TOO_LONG_PLEASE_REMOVE_CHARACTERS',
textParams: { textParams: {
'{EXTRA_CHARS_COUNT}': extraLength, '{EXTRA_CHARS_COUNT}': extraLength,
@ -454,8 +454,8 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
const secondsRemaining = nextSendDateNotReached const secondsRemaining = nextSendDateNotReached
? slowMode.nextSendDate! - nowSeconds ? slowMode.nextSendDate! - nowSeconds
: slowMode.seconds - secondsSinceLastMessage!; : slowMode.seconds - secondsSinceLastMessage!;
showError({ showDialog({
error: { data: {
message: `A wait of ${secondsRemaining} seconds is required before sending another message in this chat`, message: `A wait of ${secondsRemaining} seconds is required before sending another message in this chat`,
isSlowMode: true, isSlowMode: true,
}, },
@ -488,7 +488,7 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
requestAnimationFrame(resetComposer); requestAnimationFrame(resetComposer);
}, [ }, [
connectionState, attachments, activeVoiceRecording, isForwarding, serverTimeOffset, clearDraft, chatId, connectionState, attachments, activeVoiceRecording, isForwarding, serverTimeOffset, clearDraft, chatId,
resetComposer, stopRecordingVoice, showError, slowMode, isAdmin, sendMessage, forwardMessages, resetComposer, stopRecordingVoice, showDialog, slowMode, isAdmin, sendMessage, forwardMessages,
]); ]);
const handleStickerSelect = useCallback((sticker: ApiSticker) => { const handleStickerSelect = useCallback((sticker: ApiSticker) => {
@ -972,7 +972,7 @@ export default memo(withGlobal<OwnProps>(
'editMessage', 'editMessage',
'saveDraft', 'saveDraft',
'clearDraft', 'clearDraft',
'showError', 'showDialog',
'setStickerSearchQuery', 'setStickerSearchQuery',
'setGifSearchQuery', 'setGifSearchQuery',
'forwardMessages', 'forwardMessages',

View File

@ -5,12 +5,12 @@ import { withGlobal } from '../../lib/teact/teactn';
import { GlobalActions, GlobalState } from '../../global/types'; import { GlobalActions, GlobalState } from '../../global/types';
import { PaymentStep, ShippingOption, Price } from '../../types'; import { PaymentStep, ShippingOption, Price } from '../../types';
import { ApiError } from '../../api/types'; import { ApiError, ApiInviteInfo } from '../../api/types';
import { pick } from '../../util/iteratees'; import { pick } from '../../util/iteratees';
import { getCurrencySign } from '../middle/helpers/getCurrencySign'; import { getCurrencySign } from '../middle/helpers/getCurrencySign';
import { detectCardTypeText } from '../common/helpers/detectCardType'; import { detectCardTypeText } from '../common/helpers/detectCardType';
import { getShippingError } from '../../modules/helpers/payments'; import { getShippingErrors } from '../../modules/helpers/payments';
import usePaymentReducer, { FormState } from '../../hooks/reducers/usePaymentReducer'; import usePaymentReducer, { FormState } from '../../hooks/reducers/usePaymentReducer';
import useLang from '../../hooks/useLang'; import useLang from '../../hooks/useLang';
@ -46,7 +46,7 @@ type StateProps = {
needCardholderName?: boolean; needCardholderName?: boolean;
needCountry?: boolean; needCountry?: boolean;
needZip?: boolean; needZip?: boolean;
globalErrors?: ApiError[]; globalDialogs?: (ApiError | ApiInviteInfo)[];
}; };
type GlobalStateProps = Pick<GlobalState['payment'], 'step' | 'shippingOptions' | type GlobalStateProps = Pick<GlobalState['payment'], 'step' | 'shippingOptions' |
@ -79,7 +79,7 @@ const Invoice: FC<OwnProps & StateProps & GlobalStateProps & DispatchProps> = ({
needCountry, needCountry,
needZip, needZip,
error, error,
globalErrors, globalDialogs,
validateRequestedInfo, validateRequestedInfo,
sendPaymentForm, sendPaymentForm,
setPaymentStep, setPaymentStep,
@ -92,10 +92,10 @@ const Invoice: FC<OwnProps & StateProps & GlobalStateProps & DispatchProps> = ({
const lang = useLang(); const lang = useLang();
useEffect(() => { useEffect(() => {
if (step || error || globalErrors) { if (step || error || globalDialogs) {
setIsLoading(false); setIsLoading(false);
} }
}, [step, error, globalErrors]); }, [step, error, globalDialogs]);
useEffect(() => { useEffect(() => {
if (error && error.field) { if (error && error.field) {
@ -107,8 +107,8 @@ const Invoice: FC<OwnProps & StateProps & GlobalStateProps & DispatchProps> = ({
}); });
return; return;
} }
if (globalErrors && globalErrors.length) { if (globalDialogs && globalDialogs.length) {
const errors = getShippingError(globalErrors); const errors = getShippingErrors(globalDialogs);
paymentDispatch({ paymentDispatch({
type: 'setFormErrors', type: 'setFormErrors',
payload: { payload: {
@ -116,7 +116,7 @@ const Invoice: FC<OwnProps & StateProps & GlobalStateProps & DispatchProps> = ({
}, },
}); });
} }
}, [error, globalErrors, paymentDispatch]); }, [error, globalDialogs, paymentDispatch]);
useEffect(() => { useEffect(() => {
if (savedInfo) { if (savedInfo) {
@ -412,7 +412,7 @@ export default memo(withGlobal<OwnProps>(
needCountry, needCountry,
needZip, needZip,
error, error,
globalErrors: global.errors, globalDialogs: global.dialogs,
}; };
}, },
(setGlobal, actions): DispatchProps => { (setGlobal, actions): DispatchProps => {

View File

@ -99,7 +99,7 @@ export const INITIAL_STATE: GlobalState = {
notifications: [], notifications: [],
errors: [], dialogs: [],
activeSessions: [], activeSessions: [],

View File

@ -18,6 +18,7 @@ import {
ApiPaymentSavedInfo, ApiPaymentSavedInfo,
ApiSession, ApiSession,
ApiNewPoll, ApiNewPoll,
ApiInviteInfo,
} from '../api/types'; } from '../api/types';
import { import {
FocusDirection, FocusDirection,
@ -364,7 +365,7 @@ export type GlobalState = {
}; };
notifications: ApiNotification[]; notifications: ApiNotification[];
errors: ApiError[]; dialogs: (ApiError | ApiInviteInfo)[];
// TODO Move to settings // TODO Move to settings
activeSessions: ApiSession[]; activeSessions: ApiSession[];
@ -399,7 +400,7 @@ export type GlobalState = {
export type ActionTypes = ( export type ActionTypes = (
// system // system
'init' | 'reset' | 'disconnect' | 'initApi' | 'apiUpdate' | 'sync' | 'saveSession' | 'afterSync' | 'init' | 'reset' | 'disconnect' | 'initApi' | 'apiUpdate' | 'sync' | 'saveSession' | 'afterSync' |
'showNotification' | 'dismissNotification' | 'showError' | 'dismissError' | 'showNotification' | 'dismissNotification' | 'showDialog' | 'dismissDialog' |
// ui // ui
'toggleChatInfo' | 'setIsUiReady' | 'addRecentEmoji' | 'addRecentSticker' | 'toggleLeftColumn' | 'toggleChatInfo' | 'setIsUiReady' | 'addRecentEmoji' | 'addRecentSticker' | 'toggleLeftColumn' |
'toggleSafeLinkModal' | 'openHistoryCalendar' | 'closeHistoryCalendar' | 'disableContextMenuHint' | 'toggleSafeLinkModal' | 'openHistoryCalendar' | 'closeHistoryCalendar' | 'disableContextMenuHint' |
@ -438,6 +439,7 @@ export type ActionTypes = (
'toggleManagement' | 'closeManagement' | 'checkPublicLink' | 'updatePublicLink' | 'updatePrivateLink' | 'toggleManagement' | 'closeManagement' | 'checkPublicLink' | 'updatePublicLink' | 'updatePrivateLink' |
// groups // groups
'togglePreHistoryHidden' | 'updateChatDefaultBannedRights' | 'updateChatMemberBannedRights' | 'updateChatAdmin' | 'togglePreHistoryHidden' | 'updateChatDefaultBannedRights' | 'updateChatMemberBannedRights' | 'updateChatAdmin' |
'acceptInviteConfirmation' |
// users // users
'loadFullUser' | 'openUserInfo' | 'loadNearestCountry' | 'loadTopUsers' | 'loadContactList' | 'loadCurrentUser' | 'loadFullUser' | 'openUserInfo' | 'loadNearestCountry' | 'loadTopUsers' | 'loadContactList' | 'loadCurrentUser' |
'updateProfile' | 'checkUsername' | 'updateContact' | 'deleteUser' | 'loadUser' | 'updateProfile' | 'checkUsername' | 'updateContact' | 'deleteUser' | 'loadUser' |

View File

@ -84,7 +84,7 @@ async function answerCallbackButton(chat: ApiChat, messageId: number, data: stri
const { message, alert: isError } = result; const { message, alert: isError } = result;
if (isError) { if (isError) {
getDispatch().showError({ error: { message } }); getDispatch().showDialog({ data: { message } });
} else { } else {
getDispatch().showNotification({ message }); getDispatch().showNotification({ message });
} }

View File

@ -403,6 +403,18 @@ addReducer('openTelegramLink', (global, actions, payload) => {
} }
}); });
addReducer('acceptInviteConfirmation', (global, actions, payload) => {
const { hash } = payload!;
(async () => {
const result = await callApi('importChatInvite', { hash });
if (!result) {
return;
}
actions.openChat({ id: result.id });
})();
});
addReducer('openChatByUsername', (global, actions, payload) => { addReducer('openChatByUsername', (global, actions, payload) => {
const { username } = payload!; const { username } = payload!;

View File

@ -373,6 +373,14 @@ addReducer('apiUpdate', (global, actions, update: ApiUpdate) => {
setGlobal(global); setGlobal(global);
} }
break;
}
case 'showInvite': {
const { data } = update;
actions.showDialog({ data });
break;
} }
} }
}); });

View File

@ -59,7 +59,7 @@ addReducer('apiUpdate', (global, actions, update: ApiUpdate) => {
actions.signOut(); actions.signOut();
} }
actions.showError({ error: update.error }); actions.showDialog({ data: update.error });
break; break;
} }

View File

@ -5,6 +5,7 @@ import { GlobalState } from '../../../global/types';
import { IS_SINGLE_COLUMN_LAYOUT, IS_TABLET_COLUMN_LAYOUT } from '../../../util/environment'; import { IS_SINGLE_COLUMN_LAYOUT, IS_TABLET_COLUMN_LAYOUT } from '../../../util/environment';
import getReadableErrorText from '../../../util/getReadableErrorText'; import getReadableErrorText from '../../../util/getReadableErrorText';
import { selectCurrentMessageList } from '../../selectors'; import { selectCurrentMessageList } from '../../selectors';
import { ApiError } from '../../../api/types';
const MAX_STORED_EMOJIS = 18; // Represents two rows of recent emojis const MAX_STORED_EMOJIS = 18; // Represents two rows of recent emojis
@ -158,36 +159,38 @@ addReducer('dismissNotification', (global) => {
}; };
}); });
addReducer('showError', (global, actions, payload) => { addReducer('showDialog', (global, actions, payload) => {
const { error } = payload!; const { data } = payload!;
// Filter out errors that we don't want to show to the user // Filter out errors that we don't want to show to the user
if (!getReadableErrorText(error)) { if ('message' in data && !getReadableErrorText(data)) {
return global; return global;
} }
const newErrors = [...global.errors]; const newDialogs = [...global.dialogs];
const existingErrorIndex = newErrors.findIndex((err) => err.message === error.message); if ('message' in data) {
if (existingErrorIndex !== -1) { const existingErrorIndex = newDialogs.findIndex((err) => (err as ApiError).message === data.message);
newErrors.splice(existingErrorIndex, 1); if (existingErrorIndex !== -1) {
newDialogs.splice(existingErrorIndex, 1);
}
} }
newErrors.push(error); newDialogs.push(data);
return { return {
...global, ...global,
errors: newErrors, dialogs: newDialogs,
}; };
}); });
addReducer('dismissError', (global) => { addReducer('dismissDialog', (global) => {
const newErrors = [...global.errors]; const newDialogs = [...global.dialogs];
newErrors.pop(); newDialogs.pop();
return { return {
...global, ...global,
errors: newErrors, dialogs: newDialogs,
}; };
}); });

View File

@ -1,3 +1,5 @@
import { ApiError, ApiInviteInfo } from '../../api/types';
const STRIPE_ERRORS: Record<string, Record<string, string>> = { const STRIPE_ERRORS: Record<string, Record<string, string>> = {
missing_payment_information: { missing_payment_information: {
field: 'cardNumber', field: 'cardNumber',
@ -91,8 +93,9 @@ const SHIPPING_ERRORS: Record<string, Record<string, string>> = {
}; };
export function getShippingError(errors: Record<number, { message: string }>) { export function getShippingErrors(dialogs: (ApiError | ApiInviteInfo)[]) {
return Object.values(errors).reduce((acc, cur) => { return Object.values(dialogs).reduce((acc, cur) => {
if (!('message' in cur)) return acc;
const error = SHIPPING_ERRORS[cur.message]; const error = SHIPPING_ERRORS[cur.message];
if (error) { if (error) {
acc = { acc = {

View File

@ -52,7 +52,7 @@ if (IS_SERVICE_WORKER_SUPPORTED) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.error('[SW] ServiceWorker not available'); console.error('[SW] ServiceWorker not available');
} }
getDispatch().showError({ error: { message: 'SERVICE_WORKER_DISABLED' } }); getDispatch().showDialog({ data: { message: 'SERVICE_WORKER_DISABLED' } });
} }
} catch (err) { } catch (err) {
if (DEBUG) { if (DEBUG) {