Show confirmation modal before joining with invite links (#1225)
This commit is contained in:
parent
676e914597
commit
7c860f27b3
@ -907,12 +907,15 @@ export async function openChatByInvite(hash: string) {
|
||||
let chat: ApiChat | undefined;
|
||||
|
||||
if (result instanceof GramJs.ChatInvite) {
|
||||
const updates = await invokeRequest(new GramJs.messages.ImportChatInvite({ hash }), true);
|
||||
if (!(updates instanceof GramJs.Updates) || !updates.chats.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
chat = buildApiChatFromPreview(updates.chats[0]);
|
||||
onUpdate({
|
||||
'@type': 'showInvite',
|
||||
data: {
|
||||
title: result.title,
|
||||
hash,
|
||||
participantsCount: result.participantsCount,
|
||||
isChannel: result.channel,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
|
||||
@ -14,7 +14,7 @@ export {
|
||||
fetchChatFolders, editChatFolder, deleteChatFolder, fetchRecommendedChatFolders,
|
||||
getChatByUsername, togglePreHistoryHidden, updateChatDefaultBannedRights, updateChatMemberBannedRights,
|
||||
updateChatTitle, updateChatAbout, toggleSignatures, updateChatAdmin, fetchGroupsForDiscussion, setDiscussionGroup,
|
||||
migrateChat, openChatByInvite, fetchMembers,
|
||||
migrateChat, openChatByInvite, fetchMembers, importChatInvite,
|
||||
} from './chats';
|
||||
|
||||
export {
|
||||
|
||||
@ -74,6 +74,11 @@ export type ApiUpdateChatJoin = {
|
||||
id: number;
|
||||
};
|
||||
|
||||
export type ApiUpdateShowInvite = {
|
||||
'@type': 'showInvite';
|
||||
data: ApiInviteInfo;
|
||||
};
|
||||
|
||||
export type ApiUpdateChatLeave = {
|
||||
'@type': 'updateChatLeave';
|
||||
id: number;
|
||||
@ -311,6 +316,13 @@ export type ApiError = {
|
||||
textParams?: Record<string, string>;
|
||||
};
|
||||
|
||||
export type ApiInviteInfo = {
|
||||
title: string;
|
||||
hash: string;
|
||||
isChannel?: boolean;
|
||||
participantsCount?: number;
|
||||
};
|
||||
|
||||
export type ApiUpdateError = {
|
||||
'@type': 'error';
|
||||
error: ApiError;
|
||||
@ -395,7 +407,7 @@ export type ApiUpdate = (
|
||||
ApiUpdateDeleteScheduledMessages | ApiUpdateResetMessages |
|
||||
ApiUpdateTwoFaError | updateTwoFaStateWaitCode |
|
||||
ApiUpdateNotifySettings | ApiUpdateNotifyExceptions | ApiUpdatePeerBlocked | ApiUpdatePrivacy |
|
||||
ApiUpdateServerTimeOffset
|
||||
ApiUpdateServerTimeOffset | ApiUpdateShowInvite
|
||||
);
|
||||
|
||||
export type OnApiUpdate = (update: ApiUpdate) => void;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
export { default as MediaViewer } from '../components/mediaViewer/MediaViewer';
|
||||
|
||||
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 SafeLinkModal } from '../components/main/SafeLinkModal';
|
||||
export { default as HistoryCalendar } from '../components/main/HistoryCalendar';
|
||||
|
||||
@ -35,7 +35,7 @@ type StateProps = {
|
||||
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);
|
||||
|
||||
@ -53,7 +53,7 @@ const SettingsFoldersMain: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
notifyExceptions,
|
||||
loadRecommendedChatFolders,
|
||||
addChatFolder,
|
||||
showError,
|
||||
showDialog,
|
||||
}) => {
|
||||
const [animationData, setAnimationData] = useState<Record<string, any>>();
|
||||
const [isAnimationLoaded, setIsAnimationLoaded] = useState(false);
|
||||
@ -75,8 +75,8 @@ const SettingsFoldersMain: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
|
||||
const handleCreateFolder = useCallback(() => {
|
||||
if (Object.keys(foldersById).length >= MAX_ALLOWED_FOLDERS) {
|
||||
showError({
|
||||
error: {
|
||||
showDialog({
|
||||
data: {
|
||||
message: 'DIALOG_FILTERS_TOO_MUCH',
|
||||
},
|
||||
});
|
||||
@ -85,7 +85,7 @@ const SettingsFoldersMain: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
}
|
||||
|
||||
onCreateFolder();
|
||||
}, [foldersById, showError, onCreateFolder]);
|
||||
}, [foldersById, showDialog, onCreateFolder]);
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
@ -111,8 +111,8 @@ const SettingsFoldersMain: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
|
||||
const handleCreateFolderFromRecommended = useCallback((folder: ApiChatFolder) => {
|
||||
if (Object.keys(foldersById).length >= MAX_ALLOWED_FOLDERS) {
|
||||
showError({
|
||||
error: {
|
||||
showDialog({
|
||||
data: {
|
||||
message: 'DIALOG_FILTERS_TOO_MUCH',
|
||||
},
|
||||
});
|
||||
@ -121,7 +121,7 @@ const SettingsFoldersMain: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
}
|
||||
|
||||
addChatFolder({ folder });
|
||||
}, [foldersById, addChatFolder, showError]);
|
||||
}, [foldersById, addChatFolder, showDialog]);
|
||||
|
||||
return (
|
||||
<div className="settings-content custom-scroll">
|
||||
@ -238,5 +238,5 @@ export default memo(withGlobal<OwnProps>(
|
||||
notifyExceptions: selectNotifyExceptions(global),
|
||||
};
|
||||
},
|
||||
(setGlobal, actions): DispatchProps => pick(actions, ['loadRecommendedChatFolders', 'addChatFolder', 'showError']),
|
||||
(setGlobal, actions): DispatchProps => pick(actions, ['loadRecommendedChatFolders', 'addChatFolder', 'showDialog']),
|
||||
)(SettingsFoldersMain));
|
||||
|
||||
@ -3,11 +3,11 @@ import { Bundles } from '../../util/moduleLoader';
|
||||
|
||||
import useModuleLoader from '../../hooks/useModuleLoader';
|
||||
|
||||
const ErrorsAsync: FC = ({ isOpen }) => {
|
||||
const Errors = useModuleLoader(Bundles.Extra, 'Errors', !isOpen);
|
||||
const DialogsAsync: FC = ({ isOpen }) => {
|
||||
const Dialogs = useModuleLoader(Bundles.Extra, 'Dialogs', !isOpen);
|
||||
|
||||
// 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);
|
||||
@ -1,4 +1,4 @@
|
||||
#Errors {
|
||||
#Dialogs {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
@ -6,3 +6,11 @@
|
||||
height: 100vh;
|
||||
z-index: var(--z-modal);
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
|
||||
button {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
103
src/components/main/Dialogs.tsx
Normal file
103
src/components/main/Dialogs.tsx
Normal 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));
|
||||
@ -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));
|
||||
@ -30,7 +30,7 @@ import RightColumn from '../right/RightColumn';
|
||||
import MediaViewer from '../mediaViewer/MediaViewer.async';
|
||||
import AudioPlayer from '../middle/AudioPlayer';
|
||||
import Notifications from './Notifications.async';
|
||||
import Errors from './Errors.async';
|
||||
import Dialogs from './Dialogs.async';
|
||||
import ForwardPicker from './ForwardPicker.async';
|
||||
import SafeLinkModal from './SafeLinkModal.async';
|
||||
import HistoryCalendar from './HistoryCalendar.async';
|
||||
@ -45,7 +45,7 @@ type StateProps = {
|
||||
isMediaViewerOpen: boolean;
|
||||
isForwardModalOpen: boolean;
|
||||
hasNotifications: boolean;
|
||||
hasErrors: boolean;
|
||||
hasDialogs: boolean;
|
||||
audioMessage?: ApiMessage;
|
||||
safeLinkModalUrl?: string;
|
||||
isHistoryCalendarOpen: boolean;
|
||||
@ -71,7 +71,7 @@ const Main: FC<StateProps & DispatchProps> = ({
|
||||
isForwardModalOpen,
|
||||
animationLevel,
|
||||
hasNotifications,
|
||||
hasErrors,
|
||||
hasDialogs,
|
||||
audioMessage,
|
||||
safeLinkModalUrl,
|
||||
isHistoryCalendarOpen,
|
||||
@ -192,7 +192,7 @@ const Main: FC<StateProps & DispatchProps> = ({
|
||||
<MediaViewer isOpen={isMediaViewerOpen} />
|
||||
<ForwardPicker isOpen={isForwardModalOpen} />
|
||||
<Notifications isOpen={hasNotifications} />
|
||||
<Errors isOpen={hasErrors} />
|
||||
<Dialogs isOpen={hasDialogs} />
|
||||
{audioMessage && <AudioPlayer key={audioMessage.id} message={audioMessage} noUi />}
|
||||
<SafeLinkModal url={safeLinkModalUrl} />
|
||||
<HistoryCalendar isOpen={isHistoryCalendarOpen} />
|
||||
@ -228,7 +228,7 @@ export default memo(withGlobal(
|
||||
isMediaViewerOpen: selectIsMediaViewerOpen(global),
|
||||
isForwardModalOpen: selectIsForwardModalOpen(global),
|
||||
hasNotifications: Boolean(global.notifications.length),
|
||||
hasErrors: Boolean(global.errors.length),
|
||||
hasDialogs: Boolean(global.dialogs.length),
|
||||
audioMessage,
|
||||
safeLinkModalUrl: global.safeLinkModalUrl,
|
||||
isHistoryCalendarOpen: Boolean(global.historyCalendarSelectedAt),
|
||||
|
||||
@ -128,7 +128,7 @@ type StateProps = {
|
||||
|
||||
type DispatchProps = Pick<GlobalActions, (
|
||||
'sendMessage' | 'editMessage' | 'saveDraft' | 'forwardMessages' |
|
||||
'clearDraft' | 'showError' | 'setStickerSearchQuery' | 'setGifSearchQuery' |
|
||||
'clearDraft' | 'showDialog' | 'setStickerSearchQuery' | 'setGifSearchQuery' |
|
||||
'openPollModal' | 'closePollModal' | 'loadScheduledHistory' | 'openChat' | 'closePaymentModal' |
|
||||
'clearReceipt' | 'addRecentEmoji' | 'loadEmojiKeywords'
|
||||
)>;
|
||||
@ -189,7 +189,7 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
editMessage,
|
||||
saveDraft,
|
||||
clearDraft,
|
||||
showError,
|
||||
showDialog,
|
||||
setStickerSearchQuery,
|
||||
setGifSearchQuery,
|
||||
forwardMessages,
|
||||
@ -428,8 +428,8 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
|
||||
if (currentAttachments.length && text && text.length > CAPTION_MAX_LENGTH) {
|
||||
const extraLength = text.length - CAPTION_MAX_LENGTH;
|
||||
showError({
|
||||
error: {
|
||||
showDialog({
|
||||
data: {
|
||||
message: 'CAPTION_TOO_LONG_PLEASE_REMOVE_CHARACTERS',
|
||||
textParams: {
|
||||
'{EXTRA_CHARS_COUNT}': extraLength,
|
||||
@ -454,8 +454,8 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
const secondsRemaining = nextSendDateNotReached
|
||||
? slowMode.nextSendDate! - nowSeconds
|
||||
: slowMode.seconds - secondsSinceLastMessage!;
|
||||
showError({
|
||||
error: {
|
||||
showDialog({
|
||||
data: {
|
||||
message: `A wait of ${secondsRemaining} seconds is required before sending another message in this chat`,
|
||||
isSlowMode: true,
|
||||
},
|
||||
@ -488,7 +488,7 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
requestAnimationFrame(resetComposer);
|
||||
}, [
|
||||
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) => {
|
||||
@ -972,7 +972,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
'editMessage',
|
||||
'saveDraft',
|
||||
'clearDraft',
|
||||
'showError',
|
||||
'showDialog',
|
||||
'setStickerSearchQuery',
|
||||
'setGifSearchQuery',
|
||||
'forwardMessages',
|
||||
|
||||
@ -5,12 +5,12 @@ import { withGlobal } from '../../lib/teact/teactn';
|
||||
|
||||
import { GlobalActions, GlobalState } from '../../global/types';
|
||||
import { PaymentStep, ShippingOption, Price } from '../../types';
|
||||
import { ApiError } from '../../api/types';
|
||||
import { ApiError, ApiInviteInfo } from '../../api/types';
|
||||
|
||||
import { pick } from '../../util/iteratees';
|
||||
import { getCurrencySign } from '../middle/helpers/getCurrencySign';
|
||||
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 useLang from '../../hooks/useLang';
|
||||
|
||||
@ -46,7 +46,7 @@ type StateProps = {
|
||||
needCardholderName?: boolean;
|
||||
needCountry?: boolean;
|
||||
needZip?: boolean;
|
||||
globalErrors?: ApiError[];
|
||||
globalDialogs?: (ApiError | ApiInviteInfo)[];
|
||||
};
|
||||
|
||||
type GlobalStateProps = Pick<GlobalState['payment'], 'step' | 'shippingOptions' |
|
||||
@ -79,7 +79,7 @@ const Invoice: FC<OwnProps & StateProps & GlobalStateProps & DispatchProps> = ({
|
||||
needCountry,
|
||||
needZip,
|
||||
error,
|
||||
globalErrors,
|
||||
globalDialogs,
|
||||
validateRequestedInfo,
|
||||
sendPaymentForm,
|
||||
setPaymentStep,
|
||||
@ -92,10 +92,10 @@ const Invoice: FC<OwnProps & StateProps & GlobalStateProps & DispatchProps> = ({
|
||||
const lang = useLang();
|
||||
|
||||
useEffect(() => {
|
||||
if (step || error || globalErrors) {
|
||||
if (step || error || globalDialogs) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [step, error, globalErrors]);
|
||||
}, [step, error, globalDialogs]);
|
||||
|
||||
useEffect(() => {
|
||||
if (error && error.field) {
|
||||
@ -107,8 +107,8 @@ const Invoice: FC<OwnProps & StateProps & GlobalStateProps & DispatchProps> = ({
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (globalErrors && globalErrors.length) {
|
||||
const errors = getShippingError(globalErrors);
|
||||
if (globalDialogs && globalDialogs.length) {
|
||||
const errors = getShippingErrors(globalDialogs);
|
||||
paymentDispatch({
|
||||
type: 'setFormErrors',
|
||||
payload: {
|
||||
@ -116,7 +116,7 @@ const Invoice: FC<OwnProps & StateProps & GlobalStateProps & DispatchProps> = ({
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [error, globalErrors, paymentDispatch]);
|
||||
}, [error, globalDialogs, paymentDispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
if (savedInfo) {
|
||||
@ -412,7 +412,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
needCountry,
|
||||
needZip,
|
||||
error,
|
||||
globalErrors: global.errors,
|
||||
globalDialogs: global.dialogs,
|
||||
};
|
||||
},
|
||||
(setGlobal, actions): DispatchProps => {
|
||||
|
||||
@ -99,7 +99,7 @@ export const INITIAL_STATE: GlobalState = {
|
||||
|
||||
notifications: [],
|
||||
|
||||
errors: [],
|
||||
dialogs: [],
|
||||
|
||||
activeSessions: [],
|
||||
|
||||
|
||||
@ -18,6 +18,7 @@ import {
|
||||
ApiPaymentSavedInfo,
|
||||
ApiSession,
|
||||
ApiNewPoll,
|
||||
ApiInviteInfo,
|
||||
} from '../api/types';
|
||||
import {
|
||||
FocusDirection,
|
||||
@ -364,7 +365,7 @@ export type GlobalState = {
|
||||
};
|
||||
|
||||
notifications: ApiNotification[];
|
||||
errors: ApiError[];
|
||||
dialogs: (ApiError | ApiInviteInfo)[];
|
||||
|
||||
// TODO Move to settings
|
||||
activeSessions: ApiSession[];
|
||||
@ -399,7 +400,7 @@ export type GlobalState = {
|
||||
export type ActionTypes = (
|
||||
// system
|
||||
'init' | 'reset' | 'disconnect' | 'initApi' | 'apiUpdate' | 'sync' | 'saveSession' | 'afterSync' |
|
||||
'showNotification' | 'dismissNotification' | 'showError' | 'dismissError' |
|
||||
'showNotification' | 'dismissNotification' | 'showDialog' | 'dismissDialog' |
|
||||
// ui
|
||||
'toggleChatInfo' | 'setIsUiReady' | 'addRecentEmoji' | 'addRecentSticker' | 'toggleLeftColumn' |
|
||||
'toggleSafeLinkModal' | 'openHistoryCalendar' | 'closeHistoryCalendar' | 'disableContextMenuHint' |
|
||||
@ -438,6 +439,7 @@ export type ActionTypes = (
|
||||
'toggleManagement' | 'closeManagement' | 'checkPublicLink' | 'updatePublicLink' | 'updatePrivateLink' |
|
||||
// groups
|
||||
'togglePreHistoryHidden' | 'updateChatDefaultBannedRights' | 'updateChatMemberBannedRights' | 'updateChatAdmin' |
|
||||
'acceptInviteConfirmation' |
|
||||
// users
|
||||
'loadFullUser' | 'openUserInfo' | 'loadNearestCountry' | 'loadTopUsers' | 'loadContactList' | 'loadCurrentUser' |
|
||||
'updateProfile' | 'checkUsername' | 'updateContact' | 'deleteUser' | 'loadUser' |
|
||||
|
||||
@ -84,7 +84,7 @@ async function answerCallbackButton(chat: ApiChat, messageId: number, data: stri
|
||||
const { message, alert: isError } = result;
|
||||
|
||||
if (isError) {
|
||||
getDispatch().showError({ error: { message } });
|
||||
getDispatch().showDialog({ data: { message } });
|
||||
} else {
|
||||
getDispatch().showNotification({ message });
|
||||
}
|
||||
|
||||
@ -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) => {
|
||||
const { username } = payload!;
|
||||
|
||||
|
||||
@ -373,6 +373,14 @@ addReducer('apiUpdate', (global, actions, update: ApiUpdate) => {
|
||||
|
||||
setGlobal(global);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'showInvite': {
|
||||
const { data } = update;
|
||||
|
||||
actions.showDialog({ data });
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -59,7 +59,7 @@ addReducer('apiUpdate', (global, actions, update: ApiUpdate) => {
|
||||
actions.signOut();
|
||||
}
|
||||
|
||||
actions.showError({ error: update.error });
|
||||
actions.showDialog({ data: update.error });
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ import { GlobalState } from '../../../global/types';
|
||||
import { IS_SINGLE_COLUMN_LAYOUT, IS_TABLET_COLUMN_LAYOUT } from '../../../util/environment';
|
||||
import getReadableErrorText from '../../../util/getReadableErrorText';
|
||||
import { selectCurrentMessageList } from '../../selectors';
|
||||
import { ApiError } from '../../../api/types';
|
||||
|
||||
const MAX_STORED_EMOJIS = 18; // Represents two rows of recent emojis
|
||||
|
||||
@ -158,36 +159,38 @@ addReducer('dismissNotification', (global) => {
|
||||
};
|
||||
});
|
||||
|
||||
addReducer('showError', (global, actions, payload) => {
|
||||
const { error } = payload!;
|
||||
addReducer('showDialog', (global, actions, payload) => {
|
||||
const { data } = payload!;
|
||||
|
||||
// Filter out errors that we don't want to show to the user
|
||||
if (!getReadableErrorText(error)) {
|
||||
if ('message' in data && !getReadableErrorText(data)) {
|
||||
return global;
|
||||
}
|
||||
|
||||
const newErrors = [...global.errors];
|
||||
const existingErrorIndex = newErrors.findIndex((err) => err.message === error.message);
|
||||
if (existingErrorIndex !== -1) {
|
||||
newErrors.splice(existingErrorIndex, 1);
|
||||
const newDialogs = [...global.dialogs];
|
||||
if ('message' in data) {
|
||||
const existingErrorIndex = newDialogs.findIndex((err) => (err as ApiError).message === data.message);
|
||||
if (existingErrorIndex !== -1) {
|
||||
newDialogs.splice(existingErrorIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
newErrors.push(error);
|
||||
newDialogs.push(data);
|
||||
|
||||
return {
|
||||
...global,
|
||||
errors: newErrors,
|
||||
dialogs: newDialogs,
|
||||
};
|
||||
});
|
||||
|
||||
addReducer('dismissError', (global) => {
|
||||
const newErrors = [...global.errors];
|
||||
addReducer('dismissDialog', (global) => {
|
||||
const newDialogs = [...global.dialogs];
|
||||
|
||||
newErrors.pop();
|
||||
newDialogs.pop();
|
||||
|
||||
return {
|
||||
...global,
|
||||
errors: newErrors,
|
||||
dialogs: newDialogs,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { ApiError, ApiInviteInfo } from '../../api/types';
|
||||
|
||||
const STRIPE_ERRORS: Record<string, Record<string, string>> = {
|
||||
missing_payment_information: {
|
||||
field: 'cardNumber',
|
||||
@ -91,8 +93,9 @@ const SHIPPING_ERRORS: Record<string, Record<string, string>> = {
|
||||
};
|
||||
|
||||
|
||||
export function getShippingError(errors: Record<number, { message: string }>) {
|
||||
return Object.values(errors).reduce((acc, cur) => {
|
||||
export function getShippingErrors(dialogs: (ApiError | ApiInviteInfo)[]) {
|
||||
return Object.values(dialogs).reduce((acc, cur) => {
|
||||
if (!('message' in cur)) return acc;
|
||||
const error = SHIPPING_ERRORS[cur.message];
|
||||
if (error) {
|
||||
acc = {
|
||||
|
||||
@ -52,7 +52,7 @@ if (IS_SERVICE_WORKER_SUPPORTED) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('[SW] ServiceWorker not available');
|
||||
}
|
||||
getDispatch().showError({ error: { message: 'SERVICE_WORKER_DISABLED' } });
|
||||
getDispatch().showDialog({ data: { message: 'SERVICE_WORKER_DISABLED' } });
|
||||
}
|
||||
} catch (err) {
|
||||
if (DEBUG) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user