Mini Apps: Use key as active tab id (#5268)
This commit is contained in:
parent
87840629da
commit
00780ece5e
@ -7,7 +7,7 @@ import { getActions, getGlobal, withGlobal } from '../../../global';
|
|||||||
import type { ApiUser } from '../../../api/types';
|
import type { ApiUser } from '../../../api/types';
|
||||||
import type { WebApp } from '../../../global/types';
|
import type { WebApp } from '../../../global/types';
|
||||||
|
|
||||||
import { selectTabState, selectUser } from '../../../global/selectors';
|
import { selectActiveWebApp, selectTabState, selectUser } from '../../../global/selectors';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { unique } from '../../../util/iteratees';
|
import { unique } from '../../../util/iteratees';
|
||||||
|
|
||||||
@ -126,7 +126,7 @@ export default memo(withGlobal(
|
|||||||
const tabState = selectTabState(global);
|
const tabState = selectTabState(global);
|
||||||
const webApps = tabState.webApps;
|
const webApps = tabState.webApps;
|
||||||
|
|
||||||
const { botId } = webApps?.activeWebApp || {};
|
const { botId } = selectActiveWebApp(global) || {};
|
||||||
const { modalState, openedWebApps } = webApps || {};
|
const { modalState, openedWebApps } = webApps || {};
|
||||||
const isMinimizedState = modalState === 'minimized';
|
const isMinimizedState = modalState === 'minimized';
|
||||||
const activeTabBot = botId ? selectUser(global, botId) : undefined;
|
const activeTabBot = botId ? selectUser(global, botId) : undefined;
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import type { WebAppOutboundEvent } from '../../../types/webapp';
|
|||||||
import { getWebAppKey } from '../../../global/helpers/bots';
|
import { getWebAppKey } from '../../../global/helpers/bots';
|
||||||
import {
|
import {
|
||||||
selectCurrentChat, selectTheme, selectUser,
|
selectCurrentChat, selectTheme, selectUser,
|
||||||
|
selectWebApp,
|
||||||
} from '../../../global/selectors';
|
} from '../../../global/selectors';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import buildStyle from '../../../util/buildStyle';
|
import buildStyle from '../../../util/buildStyle';
|
||||||
@ -103,8 +104,9 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
openedWebApps, activeWebApp, openedOrderedKeys, sessionKeys, isMoreAppsTabActive,
|
openedWebApps, activeWebAppKey, openedOrderedKeys, sessionKeys, isMoreAppsTabActive,
|
||||||
} = modal || {};
|
} = modal || {};
|
||||||
|
const activeWebApp = activeWebAppKey ? openedWebApps?.[activeWebAppKey] : undefined;
|
||||||
const {
|
const {
|
||||||
isBackButtonVisible, headerColor, backgroundColor, isSettingsButtonVisible,
|
isBackButtonVisible, headerColor, backgroundColor, isSettingsButtonVisible,
|
||||||
} = activeWebApp || {};
|
} = activeWebApp || {};
|
||||||
@ -664,7 +666,8 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global, { modal }): StateProps => {
|
(global, { modal }): StateProps => {
|
||||||
const { botId: activeBotId } = modal?.activeWebApp || {};
|
const activeWebApp = modal?.activeWebAppKey ? selectWebApp(global, modal.activeWebAppKey) : undefined;
|
||||||
|
const { botId: activeBotId } = activeWebApp || {};
|
||||||
|
|
||||||
const attachBot = activeBotId ? global.attachMenu.bots[activeBotId] : undefined;
|
const attachBot = activeBotId ? global.attachMenu.bots[activeBotId] : undefined;
|
||||||
const bot = activeBotId ? selectUser(global, activeBotId) : undefined;
|
const bot = activeBotId ? selectUser(global, activeBotId) : undefined;
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import { convertToApiChatType } from '../../../global/helpers';
|
|||||||
import { getWebAppKey } from '../../../global/helpers/bots';
|
import { getWebAppKey } from '../../../global/helpers/bots';
|
||||||
import {
|
import {
|
||||||
selectCurrentChat, selectTabState, selectTheme, selectUser,
|
selectCurrentChat, selectTabState, selectTheme, selectUser,
|
||||||
|
selectWebApp,
|
||||||
} from '../../../global/selectors';
|
} from '../../../global/selectors';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import buildStyle from '../../../util/buildStyle';
|
import buildStyle from '../../../util/buildStyle';
|
||||||
@ -162,7 +163,7 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const [, setFullscreen, exitFullscreen] = useFullscreen(fullscreenElementRef, exitFullScreenCallback);
|
const [, setFullscreen, exitFullscreen] = useFullscreen(fullscreenElementRef, exitFullScreenCallback);
|
||||||
|
|
||||||
const activeWebApp = modal?.activeWebApp;
|
const activeWebApp = modal?.activeWebAppKey ? modal.openedWebApps[modal.activeWebAppKey] : undefined;
|
||||||
const activeWebAppName = activeWebApp?.appName;
|
const activeWebAppName = activeWebApp?.appName;
|
||||||
const {
|
const {
|
||||||
url, buttonText, headerColor, serverHeaderColorKey, serverHeaderColor, isBackButtonVisible,
|
url, buttonText, headerColor, serverHeaderColorKey, serverHeaderColor, isBackButtonVisible,
|
||||||
@ -962,7 +963,8 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global, { modal }): StateProps => {
|
(global, { modal }): StateProps => {
|
||||||
const { botId: activeBotId } = modal?.activeWebApp || {};
|
const activeWebApp = modal?.activeWebAppKey ? selectWebApp(global, modal.activeWebAppKey) : undefined;
|
||||||
|
const { botId: activeBotId } = activeWebApp || {};
|
||||||
const modalState = modal?.modalState;
|
const modalState = modal?.modalState;
|
||||||
|
|
||||||
const attachBot = activeBotId ? global.attachMenu.bots[activeBotId] : undefined;
|
const attachBot = activeBotId ? global.attachMenu.bots[activeBotId] : undefined;
|
||||||
|
|||||||
@ -136,10 +136,12 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
|||||||
|
|
||||||
case 'updateWebViewResultSent':
|
case 'updateWebViewResultSent':
|
||||||
Object.values(global.byTabId).forEach((tabState) => {
|
Object.values(global.byTabId).forEach((tabState) => {
|
||||||
if (tabState.webApps.activeWebApp?.queryId === update.queryId) {
|
Object.entries(tabState.webApps.openedWebApps).forEach(([webAppKey, webApp]) => {
|
||||||
actions.resetDraftReplyInfo({ tabId: tabState.id });
|
if (webApp.queryId === update.queryId) {
|
||||||
actions.closeActiveWebApp({ tabId: tabState.id });
|
actions.resetDraftReplyInfo({ tabId: tabState.id });
|
||||||
}
|
actions.closeWebApp({ key: webAppKey, tabId: tabState.id });
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,9 @@ import {
|
|||||||
updateWebApp,
|
updateWebApp,
|
||||||
} from '../../reducers/bots';
|
} from '../../reducers/bots';
|
||||||
import { updateTabState } from '../../reducers/tabs';
|
import { updateTabState } from '../../reducers/tabs';
|
||||||
import { selectCurrentMessageList, selectTabState, selectWebApp } from '../../selectors';
|
import {
|
||||||
|
selectActiveWebApp, selectCurrentMessageList, selectTabState, selectWebApp,
|
||||||
|
} from '../../selectors';
|
||||||
|
|
||||||
addActionHandler('openWebAppTab', (global, actions, payload): ActionReturnType => {
|
addActionHandler('openWebAppTab', (global, actions, payload): ActionReturnType => {
|
||||||
const {
|
const {
|
||||||
@ -53,7 +55,7 @@ addActionHandler('openMoreAppsTab', (global, actions, payload): ActionReturnType
|
|||||||
global = updateTabState(global, {
|
global = updateTabState(global, {
|
||||||
webApps: {
|
webApps: {
|
||||||
...tabState.webApps,
|
...tabState.webApps,
|
||||||
activeWebApp: undefined,
|
activeWebAppKey: undefined,
|
||||||
isMoreAppsTabActive: true,
|
isMoreAppsTabActive: true,
|
||||||
},
|
},
|
||||||
}, tabId);
|
}, tabId);
|
||||||
@ -68,14 +70,14 @@ addActionHandler('closeMoreAppsTab', (global, actions, payload): ActionReturnTyp
|
|||||||
|
|
||||||
const openedWebApps = tabState.webApps.openedWebApps;
|
const openedWebApps = tabState.webApps.openedWebApps;
|
||||||
|
|
||||||
const openedWebAppsValues = Object.values(openedWebApps);
|
const openedWebAppsKeys = Object.keys(openedWebApps);
|
||||||
const openedWebAppsCount = openedWebAppsValues.length;
|
const openedWebAppsCount = openedWebAppsKeys.length;
|
||||||
|
|
||||||
global = updateTabState(global, {
|
global = updateTabState(global, {
|
||||||
webApps: {
|
webApps: {
|
||||||
...tabState.webApps,
|
...tabState.webApps,
|
||||||
isMoreAppsTabActive: false,
|
isMoreAppsTabActive: false,
|
||||||
activeWebApp: openedWebAppsCount ? openedWebAppsValues[openedWebAppsCount - 1] : undefined,
|
activeWebAppKey: openedWebAppsCount ? openedWebAppsKeys[openedWebAppsCount - 1] : undefined,
|
||||||
isModalOpen: openedWebAppsCount > 0,
|
isModalOpen: openedWebAppsCount > 0,
|
||||||
},
|
},
|
||||||
}, tabId);
|
}, tabId);
|
||||||
@ -117,8 +119,7 @@ addActionHandler('changeWebAppModalState', (global, actions, payload): ActionRet
|
|||||||
|
|
||||||
addActionHandler('setWebAppPaymentSlug', (global, actions, payload): ActionReturnType => {
|
addActionHandler('setWebAppPaymentSlug', (global, actions, payload): ActionReturnType => {
|
||||||
const { tabId = getCurrentTabId() } = payload;
|
const { tabId = getCurrentTabId() } = payload;
|
||||||
const tabState = selectTabState(global, tabId);
|
const activeWebApp = selectActiveWebApp(global, tabId);
|
||||||
const activeWebApp = tabState.webApps.activeWebApp;
|
|
||||||
if (!activeWebApp?.url) return undefined;
|
if (!activeWebApp?.url) return undefined;
|
||||||
|
|
||||||
const key = getWebAppKey(activeWebApp);
|
const key = getWebAppKey(activeWebApp);
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import type {
|
|||||||
|
|
||||||
import { getCurrentTabId } from '../../util/establishMultitabRole';
|
import { getCurrentTabId } from '../../util/establishMultitabRole';
|
||||||
import { getWebAppKey } from '../helpers/bots';
|
import { getWebAppKey } from '../helpers/bots';
|
||||||
import { selectTabState } from '../selectors';
|
import { selectActiveWebApp, selectTabState } from '../selectors';
|
||||||
import { updateTabState } from './tabs';
|
import { updateTabState } from './tabs';
|
||||||
|
|
||||||
export function replaceInlineBotSettings<T extends GlobalState>(
|
export function replaceInlineBotSettings<T extends GlobalState>(
|
||||||
@ -55,14 +55,9 @@ export function updateWebApp <T extends GlobalState>(
|
|||||||
const updatedWebAppKey = getWebAppKey(updatedValue);
|
const updatedWebAppKey = getWebAppKey(updatedValue);
|
||||||
if (!updatedWebAppKey) return global;
|
if (!updatedWebAppKey) return global;
|
||||||
|
|
||||||
const activeWebApp = currentTabState.webApps.activeWebApp;
|
|
||||||
const activeWebAppKey = activeWebApp && getWebAppKey(activeWebApp);
|
|
||||||
global = updateTabState(global, {
|
global = updateTabState(global, {
|
||||||
webApps: {
|
webApps: {
|
||||||
...currentTabState.webApps,
|
...currentTabState.webApps,
|
||||||
...updatedWebAppKey === activeWebAppKey && {
|
|
||||||
activeWebApp: updatedValue,
|
|
||||||
},
|
|
||||||
openedWebApps: {
|
openedWebApps: {
|
||||||
...openedWebApps,
|
...openedWebApps,
|
||||||
[updatedWebAppKey]: updatedValue,
|
[updatedWebAppKey]: updatedValue,
|
||||||
@ -84,13 +79,11 @@ export function activateWebAppIfOpen<T extends GlobalState>(
|
|||||||
return global;
|
return global;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newActiveWebApp = openedWebApps[webAppKey];
|
|
||||||
|
|
||||||
global = updateTabState(global, {
|
global = updateTabState(global, {
|
||||||
webApps: {
|
webApps: {
|
||||||
...currentTabState.webApps,
|
...currentTabState.webApps,
|
||||||
isMoreAppsTabActive: false,
|
isMoreAppsTabActive: false,
|
||||||
activeWebApp: newActiveWebApp,
|
activeWebAppKey: webAppKey,
|
||||||
modalState: 'maximized',
|
modalState: 'maximized',
|
||||||
},
|
},
|
||||||
}, tabId);
|
}, tabId);
|
||||||
@ -119,7 +112,7 @@ export function addWebAppToOpenList<T extends GlobalState>(
|
|||||||
global = updateTabState(global, {
|
global = updateTabState(global, {
|
||||||
webApps: {
|
webApps: {
|
||||||
...currentTabState.webApps,
|
...currentTabState.webApps,
|
||||||
...makeActive && { activeWebApp: webApp },
|
...makeActive && { activeWebAppKey: key },
|
||||||
isMoreAppsTabActive: false,
|
isMoreAppsTabActive: false,
|
||||||
isModalOpen: openModalIfNotOpen,
|
isModalOpen: openModalIfNotOpen,
|
||||||
modalState: 'maximized',
|
modalState: 'maximized',
|
||||||
@ -139,12 +132,11 @@ export function removeActiveWebAppFromOpenList<T extends GlobalState>(
|
|||||||
global: T, ...[tabId = getCurrentTabId()]: TabArgs<T>
|
global: T, ...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||||
): T {
|
): T {
|
||||||
const currentTabState = selectTabState(global, tabId);
|
const currentTabState = selectTabState(global, tabId);
|
||||||
|
const activeWebAppKey = currentTabState.webApps.activeWebAppKey;
|
||||||
|
|
||||||
if (!currentTabState.webApps.activeWebApp) return global;
|
if (!activeWebAppKey) return global;
|
||||||
|
|
||||||
const key = getWebAppKey(currentTabState.webApps.activeWebApp);
|
return removeWebAppFromOpenList(global, activeWebAppKey, false, tabId);
|
||||||
|
|
||||||
return removeWebAppFromOpenList(global, key, false, tabId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeWebAppFromOpenList<T extends GlobalState>(
|
export function removeWebAppFromOpenList<T extends GlobalState>(
|
||||||
@ -152,7 +144,7 @@ export function removeWebAppFromOpenList<T extends GlobalState>(
|
|||||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||||
): T {
|
): T {
|
||||||
const currentTabState = selectTabState(global, tabId);
|
const currentTabState = selectTabState(global, tabId);
|
||||||
const openedWebApps = currentTabState.webApps.openedWebApps;
|
const { openedWebApps, openedOrderedKeys, activeWebAppKey } = currentTabState.webApps;
|
||||||
const webApp = openedWebApps[key];
|
const webApp = openedWebApps[key];
|
||||||
if (!webApp) return global;
|
if (!webApp) return global;
|
||||||
|
|
||||||
@ -163,26 +155,24 @@ export function removeWebAppFromOpenList<T extends GlobalState>(
|
|||||||
const updatedOpenedWebApps = { ...openedWebApps };
|
const updatedOpenedWebApps = { ...openedWebApps };
|
||||||
const removingWebAppKey = getWebAppKey(webApp);
|
const removingWebAppKey = getWebAppKey(webApp);
|
||||||
|
|
||||||
let newOpenedKeys = currentTabState.webApps.openedOrderedKeys;
|
let newOpenedKeys = openedOrderedKeys;
|
||||||
|
|
||||||
if (removingWebAppKey) {
|
if (removingWebAppKey) {
|
||||||
delete updatedOpenedWebApps[removingWebAppKey];
|
delete updatedOpenedWebApps[removingWebAppKey];
|
||||||
newOpenedKeys = currentTabState.webApps.openedOrderedKeys.filter((k) => k !== removingWebAppKey);
|
newOpenedKeys = openedOrderedKeys.filter((k) => k !== removingWebAppKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
const activeWebApp = currentTabState.webApps.activeWebApp;
|
const isRemovedAppActive = activeWebAppKey === getWebAppKey(webApp);
|
||||||
|
|
||||||
const isRemovedAppActive = activeWebApp && (getWebAppKey(activeWebApp) === getWebAppKey(webApp));
|
const openedWebAppsKeys = Object.keys(updatedOpenedWebApps);
|
||||||
|
const openedWebAppsCount = openedWebAppsKeys.length;
|
||||||
const openedWebAppsValues = Object.values(updatedOpenedWebApps);
|
|
||||||
const openedWebAppsCount = openedWebAppsValues.length;
|
|
||||||
|
|
||||||
global = updateTabState(global, {
|
global = updateTabState(global, {
|
||||||
webApps: {
|
webApps: {
|
||||||
...currentTabState.webApps,
|
...currentTabState.webApps,
|
||||||
...isRemovedAppActive && {
|
...isRemovedAppActive && {
|
||||||
activeWebApp: openedWebAppsCount
|
activeWebAppKey: openedWebAppsCount
|
||||||
? openedWebAppsValues[openedWebAppsCount - 1] : undefined,
|
? openedWebAppsKeys[openedWebAppsCount - 1] : undefined,
|
||||||
},
|
},
|
||||||
openedWebApps: updatedOpenedWebApps,
|
openedWebApps: updatedOpenedWebApps,
|
||||||
openedOrderedKeys: newOpenedKeys,
|
openedOrderedKeys: newOpenedKeys,
|
||||||
@ -203,8 +193,7 @@ export function clearOpenedWebApps<T extends GlobalState>(
|
|||||||
|
|
||||||
const webAppsNotAllowedToClose = Object.fromEntries(
|
const webAppsNotAllowedToClose = Object.fromEntries(
|
||||||
Object.entries(currentTabState.webApps.openedWebApps).filter(
|
Object.entries(currentTabState.webApps.openedWebApps).filter(
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
([, webApp]) => webApp.shouldConfirmClosing,
|
||||||
([url, webApp]) => webApp.shouldConfirmClosing,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -215,7 +204,7 @@ export function clearOpenedWebApps<T extends GlobalState>(
|
|||||||
return updateTabState(global, {
|
return updateTabState(global, {
|
||||||
webApps: {
|
webApps: {
|
||||||
...currentTabState.webApps,
|
...currentTabState.webApps,
|
||||||
activeWebApp: undefined,
|
activeWebAppKey: undefined,
|
||||||
openedWebApps: {},
|
openedWebApps: {},
|
||||||
openedOrderedKeys: [],
|
openedOrderedKeys: [],
|
||||||
sessionKeys: [],
|
sessionKeys: [],
|
||||||
@ -223,22 +212,25 @@ export function clearOpenedWebApps<T extends GlobalState>(
|
|||||||
}, tabId);
|
}, tabId);
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentActiveWebApp = currentTabState.webApps.activeWebApp;
|
const currentActiveWebApp = selectActiveWebApp(global, tabId);
|
||||||
|
|
||||||
const newActiveWebApp = currentActiveWebApp?.shouldConfirmClosing
|
const newActiveWebApp = currentActiveWebApp?.shouldConfirmClosing
|
||||||
? currentActiveWebApp : webAppsNotAllowedToCloseValues[0];
|
? currentActiveWebApp : webAppsNotAllowedToCloseValues[0];
|
||||||
|
|
||||||
newActiveWebApp.isCloseModalOpen = true;
|
const newActiveWebAppKey = getWebAppKey(newActiveWebApp);
|
||||||
|
|
||||||
const key = getWebAppKey(newActiveWebApp);
|
if (newActiveWebAppKey) {
|
||||||
|
webAppsNotAllowedToClose[newActiveWebAppKey] = {
|
||||||
if (key) webAppsNotAllowedToClose[key] = newActiveWebApp;
|
...newActiveWebApp,
|
||||||
const newOpenedKeys = currentTabState.webApps.openedOrderedKeys.filter((k) => k in webAppsNotAllowedToClose);
|
isCloseModalOpen: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const newOpenedKeys = currentTabState.webApps.openedOrderedKeys.filter((k) => webAppsNotAllowedToClose[k]);
|
||||||
|
|
||||||
return updateTabState(global, {
|
return updateTabState(global, {
|
||||||
webApps: {
|
webApps: {
|
||||||
...currentTabState.webApps,
|
...currentTabState.webApps,
|
||||||
activeWebApp: newActiveWebApp,
|
activeWebAppKey: newActiveWebAppKey,
|
||||||
isMoreAppsTabActive: false,
|
isMoreAppsTabActive: false,
|
||||||
openedWebApps: webAppsNotAllowedToClose,
|
openedWebApps: webAppsNotAllowedToClose,
|
||||||
openedOrderedKeys: newOpenedKeys,
|
openedOrderedKeys: newOpenedKeys,
|
||||||
|
|||||||
@ -156,3 +156,12 @@ export function selectWebApp<T extends GlobalState>(
|
|||||||
) {
|
) {
|
||||||
return selectTabState(global, tabId).webApps.openedWebApps[key];
|
return selectTabState(global, tabId).webApps.openedWebApps[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function selectActiveWebApp<T extends GlobalState>(
|
||||||
|
global: T, ...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||||
|
) {
|
||||||
|
const activeWebAppKey = selectTabState(global, tabId).webApps.activeWebAppKey;
|
||||||
|
if (!activeWebAppKey) return undefined;
|
||||||
|
|
||||||
|
return selectWebApp(global, activeWebAppKey, tabId);
|
||||||
|
}
|
||||||
|
|||||||
@ -695,7 +695,7 @@ export type TabState = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
webApps: {
|
webApps: {
|
||||||
activeWebApp?: WebApp;
|
activeWebAppKey?: string;
|
||||||
openedOrderedKeys: string[];
|
openedOrderedKeys: string[];
|
||||||
sessionKeys: string[];
|
sessionKeys: string[];
|
||||||
openedWebApps: Record<string, WebApp>;
|
openedWebApps: Record<string, WebApp>;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user