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;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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';
|
||||||
|
|||||||
@ -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));
|
||||||
|
|||||||
@ -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);
|
||||||
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
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 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),
|
||||||
|
|||||||
@ -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',
|
||||||
|
|||||||
@ -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 => {
|
||||||
|
|||||||
@ -99,7 +99,7 @@ export const INITIAL_STATE: GlobalState = {
|
|||||||
|
|
||||||
notifications: [],
|
notifications: [],
|
||||||
|
|
||||||
errors: [],
|
dialogs: [],
|
||||||
|
|
||||||
activeSessions: [],
|
activeSessions: [],
|
||||||
|
|
||||||
|
|||||||
@ -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' |
|
||||||
|
|||||||
@ -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 });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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!;
|
||||||
|
|
||||||
|
|||||||
@ -373,6 +373,14 @@ addReducer('apiUpdate', (global, actions, update: ApiUpdate) => {
|
|||||||
|
|
||||||
setGlobal(global);
|
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.signOut();
|
||||||
}
|
}
|
||||||
|
|
||||||
actions.showError({ error: update.error });
|
actions.showDialog({ data: update.error });
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -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 = {
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user