Management: Manage Join Requests and revoked invites (#1667)
This commit is contained in:
parent
871d83b951
commit
97891c02a5
@ -9,6 +9,7 @@ import {
|
|||||||
ApiChatMember,
|
ApiChatMember,
|
||||||
ApiRestrictionReason,
|
ApiRestrictionReason,
|
||||||
ApiExportedInvite,
|
ApiExportedInvite,
|
||||||
|
ApiChatInviteImporter,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
import { pick, pickTruthy } from '../../../util/iteratees';
|
import { pick, pickTruthy } from '../../../util/iteratees';
|
||||||
import {
|
import {
|
||||||
@ -388,7 +389,7 @@ export function buildApiChatBotCommands(botInfos: GramJs.BotInfo[]) {
|
|||||||
}, [] as ApiBotCommand[]);
|
}, [] as ApiBotCommand[]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildApiExportedInvite(invite: GramJs.ChatInviteExported) : ApiExportedInvite {
|
export function buildApiExportedInvite(invite: GramJs.ChatInviteExported): ApiExportedInvite {
|
||||||
const {
|
const {
|
||||||
revoked,
|
revoked,
|
||||||
date,
|
date,
|
||||||
@ -401,6 +402,7 @@ export function buildApiExportedInvite(invite: GramJs.ChatInviteExported) : ApiE
|
|||||||
requested,
|
requested,
|
||||||
requestNeeded,
|
requestNeeded,
|
||||||
title,
|
title,
|
||||||
|
adminId,
|
||||||
} = invite;
|
} = invite;
|
||||||
return {
|
return {
|
||||||
isRevoked: revoked,
|
isRevoked: revoked,
|
||||||
@ -414,5 +416,21 @@ export function buildApiExportedInvite(invite: GramJs.ChatInviteExported) : ApiE
|
|||||||
isRequestNeeded: requestNeeded,
|
isRequestNeeded: requestNeeded,
|
||||||
requested,
|
requested,
|
||||||
title,
|
title,
|
||||||
|
adminId: buildApiPeerId(adminId, 'user'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildChatInviteImporter(importer: GramJs.ChatInviteImporter): ApiChatInviteImporter {
|
||||||
|
const {
|
||||||
|
userId,
|
||||||
|
date,
|
||||||
|
about,
|
||||||
|
requested,
|
||||||
|
} = importer;
|
||||||
|
return {
|
||||||
|
userId: buildApiPeerId(userId, 'user'),
|
||||||
|
date,
|
||||||
|
about,
|
||||||
|
isRequested: requested,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,7 +26,6 @@ import {
|
|||||||
buildApiChatFolder,
|
buildApiChatFolder,
|
||||||
buildApiChatFolderFromSuggested,
|
buildApiChatFolderFromSuggested,
|
||||||
buildApiChatBotCommands,
|
buildApiChatBotCommands,
|
||||||
buildApiExportedInvite,
|
|
||||||
} from '../apiBuilders/chats';
|
} from '../apiBuilders/chats';
|
||||||
import { buildApiMessage, buildMessageDraft } from '../apiBuilders/messages';
|
import { buildApiMessage, buildMessageDraft } from '../apiBuilders/messages';
|
||||||
import { buildApiUser, buildApiUsersAndStatuses } from '../apiBuilders/users';
|
import { buildApiUser, buildApiUsersAndStatuses } from '../apiBuilders/users';
|
||||||
@ -343,6 +342,8 @@ async function getFullChatInfo(chatId: string): Promise<{
|
|||||||
botInfo,
|
botInfo,
|
||||||
call,
|
call,
|
||||||
availableReactions,
|
availableReactions,
|
||||||
|
recentRequesters,
|
||||||
|
requestsPending,
|
||||||
} = result.fullChat;
|
} = result.fullChat;
|
||||||
|
|
||||||
const members = buildChatMembers(participants);
|
const members = buildChatMembers(participants);
|
||||||
@ -361,6 +362,8 @@ async function getFullChatInfo(chatId: string): Promise<{
|
|||||||
}),
|
}),
|
||||||
groupCallId: call?.id.toString(),
|
groupCallId: call?.id.toString(),
|
||||||
enabledReactions: availableReactions,
|
enabledReactions: availableReactions,
|
||||||
|
requestsPending,
|
||||||
|
recentRequesterIds: recentRequesters?.map((userId) => buildApiPeerId(userId, 'user')),
|
||||||
},
|
},
|
||||||
users: result.users.map(buildApiUser).filter<ApiUser>(Boolean as any),
|
users: result.users.map(buildApiUser).filter<ApiUser>(Boolean as any),
|
||||||
groupCall: call ? {
|
groupCall: call ? {
|
||||||
@ -408,6 +411,8 @@ async function getFullChannelInfo(
|
|||||||
botInfo,
|
botInfo,
|
||||||
availableReactions,
|
availableReactions,
|
||||||
defaultSendAs,
|
defaultSendAs,
|
||||||
|
requestsPending,
|
||||||
|
recentRequesters,
|
||||||
} = result.fullChat;
|
} = result.fullChat;
|
||||||
|
|
||||||
const inviteLink = exportedInvite instanceof GramJs.ChatInviteExported
|
const inviteLink = exportedInvite instanceof GramJs.ChatInviteExported
|
||||||
@ -460,6 +465,8 @@ async function getFullChannelInfo(
|
|||||||
botCommands,
|
botCommands,
|
||||||
enabledReactions: availableReactions,
|
enabledReactions: availableReactions,
|
||||||
sendAsId: defaultSendAs ? getApiChatIdFromMtpPeer(defaultSendAs) : undefined,
|
sendAsId: defaultSendAs ? getApiChatIdFromMtpPeer(defaultSendAs) : undefined,
|
||||||
|
requestsPending,
|
||||||
|
recentRequesterIds: recentRequesters?.map((userId) => buildApiPeerId(userId, 'user')),
|
||||||
},
|
},
|
||||||
users: [...(users || []), ...(bannedUsers || []), ...(adminUsers || [])],
|
users: [...(users || []), ...(bannedUsers || []), ...(adminUsers || [])],
|
||||||
groupCall: call ? {
|
groupCall: call ? {
|
||||||
@ -1012,7 +1019,7 @@ export async function openChatByInvite(hash: string) {
|
|||||||
|
|
||||||
if (result instanceof GramJs.ChatInvite) {
|
if (result instanceof GramJs.ChatInvite) {
|
||||||
const {
|
const {
|
||||||
photo, participantsCount, title, channel, requestNeeded, about,
|
photo, participantsCount, title, channel, requestNeeded, about, megagroup,
|
||||||
} = result;
|
} = result;
|
||||||
|
|
||||||
if (photo instanceof GramJs.Photo) {
|
if (photo instanceof GramJs.Photo) {
|
||||||
@ -1026,7 +1033,7 @@ export async function openChatByInvite(hash: string) {
|
|||||||
about,
|
about,
|
||||||
hash,
|
hash,
|
||||||
participantsCount,
|
participantsCount,
|
||||||
isChannel: channel,
|
isChannel: channel && !megagroup,
|
||||||
isRequestNeeded: requestNeeded,
|
isRequestNeeded: requestNeeded,
|
||||||
...(photo instanceof GramJs.Photo && { photo: buildApiPhoto(photo) }),
|
...(photo instanceof GramJs.Photo && { photo: buildApiPhoto(photo) }),
|
||||||
},
|
},
|
||||||
@ -1139,84 +1146,6 @@ function updateLocalDb(result: (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchExportedChatInvites({
|
|
||||||
peer, admin, limit = 0, isRevoked,
|
|
||||||
}: { peer: ApiChat; admin: ApiUser; limit: number; isRevoked?: boolean }) {
|
|
||||||
const exportedInvites = await invokeRequest(new GramJs.messages.GetExportedChatInvites({
|
|
||||||
peer: buildInputPeer(peer.id, peer.accessHash),
|
|
||||||
adminId: buildInputEntity(admin.id, admin.accessHash) as GramJs.InputUser,
|
|
||||||
limit,
|
|
||||||
revoked: isRevoked || undefined,
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (!exportedInvites) return undefined;
|
|
||||||
|
|
||||||
return exportedInvites.invites.map(buildApiExportedInvite);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function editExportedChatInvite({
|
|
||||||
peer, isRevoked, link, expireDate, usageLimit, isRequestNeeded, title,
|
|
||||||
}: {
|
|
||||||
peer: ApiChat;
|
|
||||||
isRevoked?: boolean;
|
|
||||||
link: string;
|
|
||||||
expireDate?: number;
|
|
||||||
usageLimit?: number;
|
|
||||||
isRequestNeeded?: boolean;
|
|
||||||
title?: string;
|
|
||||||
}) {
|
|
||||||
const invite = await invokeRequest(new GramJs.messages.EditExportedChatInvite({
|
|
||||||
link,
|
|
||||||
peer: buildInputPeer(peer.id, peer.accessHash),
|
|
||||||
expireDate,
|
|
||||||
usageLimit: !isRequestNeeded ? usageLimit : undefined,
|
|
||||||
requestNeeded: isRequestNeeded,
|
|
||||||
title,
|
|
||||||
revoked: isRevoked || undefined,
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (!invite) return undefined;
|
|
||||||
|
|
||||||
if (invite instanceof GramJs.messages.ExportedChatInvite) {
|
|
||||||
const replaceInvite = buildApiExportedInvite(invite.invite);
|
|
||||||
return {
|
|
||||||
oldInvite: replaceInvite,
|
|
||||||
newInvite: replaceInvite,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (invite instanceof GramJs.messages.ExportedChatInviteReplaced) {
|
|
||||||
const oldInvite = buildApiExportedInvite(invite.invite);
|
|
||||||
const newInvite = buildApiExportedInvite(invite.newInvite);
|
|
||||||
return {
|
|
||||||
oldInvite,
|
|
||||||
newInvite,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function exportChatInvite({
|
|
||||||
peer, expireDate, usageLimit, isRequestNeeded, title,
|
|
||||||
}: {
|
|
||||||
peer: ApiChat;
|
|
||||||
expireDate?: number;
|
|
||||||
usageLimit?: number;
|
|
||||||
isRequestNeeded?: boolean;
|
|
||||||
title?: string;
|
|
||||||
}) {
|
|
||||||
const invite = await invokeRequest(new GramJs.messages.ExportChatInvite({
|
|
||||||
peer: buildInputPeer(peer.id, peer.accessHash),
|
|
||||||
expireDate,
|
|
||||||
usageLimit: !isRequestNeeded ? usageLimit : undefined,
|
|
||||||
requestNeeded: isRequestNeeded || undefined,
|
|
||||||
title,
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (!invite) return undefined;
|
|
||||||
return buildApiExportedInvite(invite);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function importChatInvite({ hash }: { hash: string }) {
|
export async function importChatInvite({ hash }: { hash: string }) {
|
||||||
const updates = await invokeRequest(new GramJs.messages.ImportChatInvite({ hash }));
|
const updates = await invokeRequest(new GramJs.messages.ImportChatInvite({ hash }));
|
||||||
if (!(updates instanceof GramJs.Updates) || !updates.chats.length) {
|
if (!(updates instanceof GramJs.Updates) || !updates.chats.length) {
|
||||||
|
|||||||
@ -15,7 +15,6 @@ export {
|
|||||||
getChatByUsername, togglePreHistoryHidden, updateChatDefaultBannedRights, updateChatMemberBannedRights,
|
getChatByUsername, togglePreHistoryHidden, updateChatDefaultBannedRights, updateChatMemberBannedRights,
|
||||||
updateChatTitle, updateChatAbout, toggleSignatures, updateChatAdmin, fetchGroupsForDiscussion, setDiscussionGroup,
|
updateChatTitle, updateChatAbout, toggleSignatures, updateChatAdmin, fetchGroupsForDiscussion, setDiscussionGroup,
|
||||||
migrateChat, openChatByInvite, fetchMembers, importChatInvite, addChatMembers, deleteChatMember, toggleIsProtected,
|
migrateChat, openChatByInvite, fetchMembers, importChatInvite, addChatMembers, deleteChatMember, toggleIsProtected,
|
||||||
fetchExportedChatInvites, editExportedChatInvite, exportChatInvite,
|
|
||||||
} from './chats';
|
} from './chats';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@ -40,6 +39,8 @@ export {
|
|||||||
|
|
||||||
export {
|
export {
|
||||||
checkChatUsername, setChatUsername, updatePrivateLink,
|
checkChatUsername, setChatUsername, updatePrivateLink,
|
||||||
|
fetchExportedChatInvites, editExportedChatInvite, exportChatInvite, deleteExportedChatInvite,
|
||||||
|
deleteRevokedExportedChatInvites, fetchChatInviteImporters, hideChatJoinRequest, hideAllChatJoinRequests,
|
||||||
} from './management';
|
} from './management';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|||||||
@ -2,7 +2,9 @@ import { Api as GramJs } from '../../../lib/gramjs';
|
|||||||
|
|
||||||
import { invokeRequest } from './client';
|
import { invokeRequest } from './client';
|
||||||
import { buildInputEntity, buildInputPeer } from '../gramjsBuilders';
|
import { buildInputEntity, buildInputPeer } from '../gramjsBuilders';
|
||||||
import { ApiChat, OnApiUpdate } from '../../types';
|
import { ApiChat, ApiUser, OnApiUpdate } from '../../types';
|
||||||
|
import { addEntitiesWithPhotosToLocalDb } from '../helpers';
|
||||||
|
import { buildApiExportedInvite, buildChatInviteImporter } from '../apiBuilders/chats';
|
||||||
|
|
||||||
let onUpdate: OnApiUpdate;
|
let onUpdate: OnApiUpdate;
|
||||||
|
|
||||||
@ -59,3 +61,160 @@ export async function updatePrivateLink({
|
|||||||
|
|
||||||
return result.link;
|
return result.link;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchExportedChatInvites({
|
||||||
|
peer, admin, limit = 0, isRevoked,
|
||||||
|
}: { peer: ApiChat; admin: ApiUser; limit: number; isRevoked?: boolean }) {
|
||||||
|
const exportedInvites = await invokeRequest(new GramJs.messages.GetExportedChatInvites({
|
||||||
|
peer: buildInputPeer(peer.id, peer.accessHash),
|
||||||
|
adminId: buildInputEntity(admin.id, admin.accessHash) as GramJs.InputUser,
|
||||||
|
limit,
|
||||||
|
revoked: isRevoked || undefined,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!exportedInvites) return undefined;
|
||||||
|
addEntitiesWithPhotosToLocalDb(exportedInvites.users);
|
||||||
|
return exportedInvites.invites.map(buildApiExportedInvite);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function editExportedChatInvite({
|
||||||
|
peer, isRevoked, link, expireDate, usageLimit, isRequestNeeded, title,
|
||||||
|
}: {
|
||||||
|
peer: ApiChat;
|
||||||
|
isRevoked?: boolean;
|
||||||
|
link: string;
|
||||||
|
expireDate?: number;
|
||||||
|
usageLimit?: number;
|
||||||
|
isRequestNeeded?: boolean;
|
||||||
|
title?: string;
|
||||||
|
}) {
|
||||||
|
const invite = await invokeRequest(new GramJs.messages.EditExportedChatInvite({
|
||||||
|
link,
|
||||||
|
peer: buildInputPeer(peer.id, peer.accessHash),
|
||||||
|
expireDate,
|
||||||
|
usageLimit: !isRequestNeeded ? usageLimit : undefined,
|
||||||
|
requestNeeded: isRequestNeeded,
|
||||||
|
title,
|
||||||
|
revoked: isRevoked || undefined,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!invite) return undefined;
|
||||||
|
|
||||||
|
addEntitiesWithPhotosToLocalDb(invite.users);
|
||||||
|
if (invite instanceof GramJs.messages.ExportedChatInvite) {
|
||||||
|
const replaceInvite = buildApiExportedInvite(invite.invite);
|
||||||
|
return {
|
||||||
|
oldInvite: replaceInvite,
|
||||||
|
newInvite: replaceInvite,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (invite instanceof GramJs.messages.ExportedChatInviteReplaced) {
|
||||||
|
const oldInvite = buildApiExportedInvite(invite.invite);
|
||||||
|
const newInvite = buildApiExportedInvite(invite.newInvite);
|
||||||
|
return {
|
||||||
|
oldInvite,
|
||||||
|
newInvite,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function exportChatInvite({
|
||||||
|
peer, expireDate, usageLimit, isRequestNeeded, title,
|
||||||
|
}: {
|
||||||
|
peer: ApiChat;
|
||||||
|
expireDate?: number;
|
||||||
|
usageLimit?: number;
|
||||||
|
isRequestNeeded?: boolean;
|
||||||
|
title?: string;
|
||||||
|
}) {
|
||||||
|
const invite = await invokeRequest(new GramJs.messages.ExportChatInvite({
|
||||||
|
peer: buildInputPeer(peer.id, peer.accessHash),
|
||||||
|
expireDate,
|
||||||
|
usageLimit: !isRequestNeeded ? usageLimit : undefined,
|
||||||
|
requestNeeded: isRequestNeeded || undefined,
|
||||||
|
title,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!invite) return undefined;
|
||||||
|
return buildApiExportedInvite(invite);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteExportedChatInvite({
|
||||||
|
peer, link,
|
||||||
|
}: {
|
||||||
|
peer: ApiChat; link: string;
|
||||||
|
}) {
|
||||||
|
const result = await invokeRequest(new GramJs.messages.DeleteExportedChatInvite({
|
||||||
|
peer: buildInputPeer(peer.id, peer.accessHash),
|
||||||
|
link,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteRevokedExportedChatInvites({
|
||||||
|
peer, admin,
|
||||||
|
}: {
|
||||||
|
peer: ApiChat; admin: ApiUser;
|
||||||
|
}) {
|
||||||
|
const result = await invokeRequest(new GramJs.messages.DeleteRevokedExportedChatInvites({
|
||||||
|
peer: buildInputPeer(peer.id, peer.accessHash),
|
||||||
|
adminId: buildInputEntity(admin.id, admin.accessHash) as GramJs.InputUser,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchChatInviteImporters({
|
||||||
|
peer, link, offsetDate = 0, offsetUser, limit = 0, isRequested,
|
||||||
|
}: {
|
||||||
|
peer: ApiChat; link?: string; offsetDate: number; offsetUser?: ApiUser; limit: number; isRequested?: boolean;
|
||||||
|
}) {
|
||||||
|
const result = await invokeRequest(new GramJs.messages.GetChatInviteImporters({
|
||||||
|
peer: buildInputPeer(peer.id, peer.accessHash),
|
||||||
|
link,
|
||||||
|
offsetDate,
|
||||||
|
offsetUser: offsetUser
|
||||||
|
? buildInputEntity(offsetUser.id, offsetUser.accessHash) as GramJs.InputUser : new GramJs.InputUserEmpty(),
|
||||||
|
limit,
|
||||||
|
requested: isRequested || undefined,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!result) return undefined;
|
||||||
|
addEntitiesWithPhotosToLocalDb(result.users);
|
||||||
|
return result.importers.map((importer) => buildChatInviteImporter(importer));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hideChatJoinRequest({
|
||||||
|
peer,
|
||||||
|
user,
|
||||||
|
isApproved,
|
||||||
|
}: {
|
||||||
|
peer: ApiChat;
|
||||||
|
user: ApiUser;
|
||||||
|
isApproved: boolean;
|
||||||
|
}) {
|
||||||
|
return invokeRequest(new GramJs.messages.HideChatJoinRequest({
|
||||||
|
peer: buildInputPeer(peer.id, peer.accessHash),
|
||||||
|
userId: buildInputEntity(user.id, user.accessHash) as GramJs.InputUser,
|
||||||
|
approved: isApproved || undefined,
|
||||||
|
}), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hideAllChatJoinRequests({
|
||||||
|
peer,
|
||||||
|
isApproved,
|
||||||
|
link,
|
||||||
|
}: {
|
||||||
|
peer: ApiChat;
|
||||||
|
isApproved: boolean;
|
||||||
|
link?: string;
|
||||||
|
}) {
|
||||||
|
return invokeRequest(new GramJs.messages.HideAllChatJoinRequests({
|
||||||
|
peer: buildInputPeer(peer.id, peer.accessHash),
|
||||||
|
approved: isApproved || undefined,
|
||||||
|
link,
|
||||||
|
}), true);
|
||||||
|
}
|
||||||
|
|||||||
@ -879,6 +879,20 @@ export function updater(update: Update, originRequest?: GramJs.AnyRequest) {
|
|||||||
groupCallId: getGroupCallId(update.call),
|
groupCallId: getGroupCallId(update.call),
|
||||||
participants: update.participants.map(buildApiGroupCallParticipant),
|
participants: update.participants.map(buildApiGroupCallParticipant),
|
||||||
});
|
});
|
||||||
|
} else if (update instanceof GramJs.UpdatePendingJoinRequests) {
|
||||||
|
// eslint-disable-next-line no-underscore-dangle
|
||||||
|
const entities = update._entities;
|
||||||
|
if (entities) {
|
||||||
|
addEntitiesWithPhotosToLocalDb(entities);
|
||||||
|
dispatchUserAndChatUpdates(entities);
|
||||||
|
}
|
||||||
|
|
||||||
|
onUpdate({
|
||||||
|
'@type': 'updatePendingJoinRequests',
|
||||||
|
chatId: getApiChatIdFromMtpPeer(update.peer),
|
||||||
|
recentRequesterIds: update.recentRequesters.map((id) => buildApiPeerId(id, 'user')),
|
||||||
|
requestsPending: update.requestsPending,
|
||||||
|
});
|
||||||
} else if (DEBUG) {
|
} else if (DEBUG) {
|
||||||
const params = typeof update === 'object' && 'className' in update ? update.className : update;
|
const params = typeof update === 'object' && 'className' in update ? update.className : update;
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { ApiMessage, ApiPhoto } from './messages';
|
import { ApiMessage, ApiPhoto } from './messages';
|
||||||
import { ApiBotCommand } from './bots';
|
import { ApiBotCommand } from './bots';
|
||||||
|
import { ApiChatInviteImporter } from './misc';
|
||||||
|
|
||||||
type ApiChatType = (
|
type ApiChatType = (
|
||||||
'chatTypePrivate' | 'chatTypeSecret' |
|
'chatTypePrivate' | 'chatTypeSecret' |
|
||||||
@ -57,6 +58,7 @@ export interface ApiChat {
|
|||||||
// Obtained with UpdateUserTyping or UpdateChatUserTyping updates
|
// Obtained with UpdateUserTyping or UpdateChatUserTyping updates
|
||||||
typingStatus?: ApiTypingStatus;
|
typingStatus?: ApiTypingStatus;
|
||||||
|
|
||||||
|
joinRequests?: ApiChatInviteImporter[];
|
||||||
sendAsIds?: string[];
|
sendAsIds?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,6 +91,8 @@ export interface ApiChatFullInfo {
|
|||||||
botCommands?: ApiBotCommand[];
|
botCommands?: ApiBotCommand[];
|
||||||
enabledReactions?: string[];
|
enabledReactions?: string[];
|
||||||
sendAsId?: string;
|
sendAsId?: string;
|
||||||
|
recentRequesterIds?: string[];
|
||||||
|
requestsPending?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiChatMember {
|
export interface ApiChatMember {
|
||||||
|
|||||||
@ -109,6 +109,14 @@ export type ApiExportedInvite = {
|
|||||||
isRequestNeeded?: boolean;
|
isRequestNeeded?: boolean;
|
||||||
requested?: number;
|
requested?: number;
|
||||||
title?: string;
|
title?: string;
|
||||||
|
adminId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ApiChatInviteImporter = {
|
||||||
|
userId: string;
|
||||||
|
date: number;
|
||||||
|
isRequested?: boolean;
|
||||||
|
about?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface ApiCountry {
|
export interface ApiCountry {
|
||||||
|
|||||||
@ -420,6 +420,13 @@ export type ApiUpdateGroupCallParticipants = {
|
|||||||
nextOffset?: string;
|
nextOffset?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ApiUpdatePendingJoinRequests = {
|
||||||
|
'@type': 'updatePendingJoinRequests';
|
||||||
|
chatId: string;
|
||||||
|
recentRequesterIds: string[];
|
||||||
|
requestsPending: number;
|
||||||
|
};
|
||||||
|
|
||||||
export type ApiUpdateGroupCallConnection = {
|
export type ApiUpdateGroupCallConnection = {
|
||||||
'@type': 'updateGroupCallConnection';
|
'@type': 'updateGroupCallConnection';
|
||||||
data: GroupCallConnectionData;
|
data: GroupCallConnectionData;
|
||||||
@ -459,7 +466,8 @@ export type ApiUpdate = (
|
|||||||
ApiUpdateNotifySettings | ApiUpdateNotifyExceptions | ApiUpdatePeerBlocked | ApiUpdatePrivacy |
|
ApiUpdateNotifySettings | ApiUpdateNotifyExceptions | ApiUpdatePeerBlocked | ApiUpdatePrivacy |
|
||||||
ApiUpdateServerTimeOffset | ApiUpdateShowInvite | ApiUpdateMessageReactions |
|
ApiUpdateServerTimeOffset | ApiUpdateShowInvite | ApiUpdateMessageReactions |
|
||||||
ApiUpdateGroupCallParticipants | ApiUpdateGroupCallConnection | ApiUpdateGroupCall | ApiUpdateGroupCallStreams |
|
ApiUpdateGroupCallParticipants | ApiUpdateGroupCallConnection | ApiUpdateGroupCall | ApiUpdateGroupCallStreams |
|
||||||
ApiUpdateGroupCallConnectionState | ApiUpdateGroupCallLeavePresentation | ApiUpdateGroupCallChatId
|
ApiUpdateGroupCallConnectionState | ApiUpdateGroupCallLeavePresentation | ApiUpdateGroupCallChatId |
|
||||||
|
ApiUpdatePendingJoinRequests
|
||||||
);
|
);
|
||||||
|
|
||||||
export type OnApiUpdate = (update: ApiUpdate) => void;
|
export type OnApiUpdate = (update: ApiUpdate) => void;
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import { getDispatch, withGlobal } from '../../lib/teact/teactn';
|
|||||||
|
|
||||||
import { MessageListType } from '../../global/types';
|
import { MessageListType } from '../../global/types';
|
||||||
import { MAIN_THREAD_ID } from '../../api/types';
|
import { MAIN_THREAD_ID } from '../../api/types';
|
||||||
import { IAnchorPosition } from '../../types';
|
import { IAnchorPosition, ManagementScreens } from '../../types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ARE_CALLS_SUPPORTED, IS_MAC_OS, IS_PWA, IS_SINGLE_COLUMN_LAYOUT,
|
ARE_CALLS_SUPPORTED, IS_MAC_OS, IS_PWA, IS_SINGLE_COLUMN_LAYOUT,
|
||||||
@ -53,6 +53,7 @@ interface StateProps {
|
|||||||
canLeave?: boolean;
|
canLeave?: boolean;
|
||||||
canEnterVoiceChat?: boolean;
|
canEnterVoiceChat?: boolean;
|
||||||
canCreateVoiceChat?: boolean;
|
canCreateVoiceChat?: boolean;
|
||||||
|
pendingJoinRequests?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chrome breaks layout when focusing input during transition
|
// Chrome breaks layout when focusing input during transition
|
||||||
@ -72,6 +73,7 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
|
|||||||
canLeave,
|
canLeave,
|
||||||
canEnterVoiceChat,
|
canEnterVoiceChat,
|
||||||
canCreateVoiceChat,
|
canCreateVoiceChat,
|
||||||
|
pendingJoinRequests,
|
||||||
isRightColumnShown,
|
isRightColumnShown,
|
||||||
canExpandActions,
|
canExpandActions,
|
||||||
}) => {
|
}) => {
|
||||||
@ -81,6 +83,7 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
|
|||||||
openLocalTextSearch,
|
openLocalTextSearch,
|
||||||
restartBot,
|
restartBot,
|
||||||
openCallFallbackConfirm,
|
openCallFallbackConfirm,
|
||||||
|
requestNextManagementScreen,
|
||||||
} = getDispatch();
|
} = getDispatch();
|
||||||
|
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
@ -114,6 +117,10 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
|
|||||||
restartBot({ chatId });
|
restartBot({ chatId });
|
||||||
}, [chatId, restartBot]);
|
}, [chatId, restartBot]);
|
||||||
|
|
||||||
|
const handleJoinRequestsClick = useCallback(() => {
|
||||||
|
requestNextManagementScreen({ screen: ManagementScreens.JoinRequests });
|
||||||
|
}, [requestNextManagementScreen]);
|
||||||
|
|
||||||
const handleSearchClick = useCallback(() => {
|
const handleSearchClick = useCallback(() => {
|
||||||
openLocalTextSearch();
|
openLocalTextSearch();
|
||||||
|
|
||||||
@ -213,6 +220,20 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{Boolean(pendingJoinRequests) && (
|
||||||
|
<Button
|
||||||
|
round
|
||||||
|
className="badge-button"
|
||||||
|
ripple={isRightColumnShown}
|
||||||
|
color="translucent"
|
||||||
|
size="smaller"
|
||||||
|
onClick={handleJoinRequestsClick}
|
||||||
|
ariaLabel={isChannel ? lang('SubscribeRequests') : lang('MemberRequests')}
|
||||||
|
>
|
||||||
|
<i className="icon-user" />
|
||||||
|
<div className="badge">{pendingJoinRequests}</div>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
<Button
|
<Button
|
||||||
ref={menuButtonRef}
|
ref={menuButtonRef}
|
||||||
className={isMenuOpen ? 'active' : ''}
|
className={isMenuOpen ? 'active' : ''}
|
||||||
@ -282,6 +303,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const canEnterVoiceChat = ARE_CALLS_SUPPORTED && chat.isCallActive;
|
const canEnterVoiceChat = ARE_CALLS_SUPPORTED && chat.isCallActive;
|
||||||
const canCreateVoiceChat = ARE_CALLS_SUPPORTED && !chat.isCallActive
|
const canCreateVoiceChat = ARE_CALLS_SUPPORTED && !chat.isCallActive
|
||||||
&& (chat.adminRights?.manageCall || (chat.isCreator && isChatBasicGroup(chat)));
|
&& (chat.adminRights?.manageCall || (chat.isCreator && isChatBasicGroup(chat)));
|
||||||
|
const pendingJoinRequests = chat.fullInfo?.requestsPending;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
noMenu: false,
|
noMenu: false,
|
||||||
@ -296,6 +318,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
canLeave,
|
canLeave,
|
||||||
canEnterVoiceChat,
|
canEnterVoiceChat,
|
||||||
canCreateVoiceChat,
|
canCreateVoiceChat,
|
||||||
|
pendingJoinRequests,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(HeaderActions));
|
)(HeaderActions));
|
||||||
|
|||||||
@ -540,6 +540,27 @@
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.badge-button {
|
||||||
|
position: relative;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
position: absolute;
|
||||||
|
min-width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
background: var(--color-primary);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
color: white;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
line-height: 1rem;
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.HeaderPinnedMessage-wrapper, .HeaderActions {
|
.HeaderPinnedMessage-wrapper, .HeaderActions {
|
||||||
|
|||||||
@ -38,6 +38,7 @@ type StateProps = {
|
|||||||
currentProfileUserId?: string;
|
currentProfileUserId?: string;
|
||||||
isChatSelected: boolean;
|
isChatSelected: boolean;
|
||||||
shouldSkipHistoryAnimations?: boolean;
|
shouldSkipHistoryAnimations?: boolean;
|
||||||
|
nextManagementScreen?: ManagementScreens;
|
||||||
};
|
};
|
||||||
|
|
||||||
const COLUMN_CLOSE_DELAY_MS = 300;
|
const COLUMN_CLOSE_DELAY_MS = 300;
|
||||||
@ -58,6 +59,7 @@ const RightColumn: FC<StateProps> = ({
|
|||||||
currentProfileUserId,
|
currentProfileUserId,
|
||||||
isChatSelected,
|
isChatSelected,
|
||||||
shouldSkipHistoryAnimations,
|
shouldSkipHistoryAnimations,
|
||||||
|
nextManagementScreen,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
toggleChatInfo,
|
toggleChatInfo,
|
||||||
@ -70,6 +72,8 @@ const RightColumn: FC<StateProps> = ({
|
|||||||
addChatMembers,
|
addChatMembers,
|
||||||
setNewChatMembersDialogState,
|
setNewChatMembersDialogState,
|
||||||
setEditingExportedInvite,
|
setEditingExportedInvite,
|
||||||
|
setOpenedInviteInfo,
|
||||||
|
requestNextManagementScreen,
|
||||||
} = getDispatch();
|
} = getDispatch();
|
||||||
|
|
||||||
const { width: windowWidth } = useWindowSize();
|
const { width: windowWidth } = useWindowSize();
|
||||||
@ -126,6 +130,7 @@ const RightColumn: FC<StateProps> = ({
|
|||||||
case ManagementScreens.GroupMembers:
|
case ManagementScreens.GroupMembers:
|
||||||
case ManagementScreens.Invites:
|
case ManagementScreens.Invites:
|
||||||
case ManagementScreens.Reactions:
|
case ManagementScreens.Reactions:
|
||||||
|
case ManagementScreens.JoinRequests:
|
||||||
setManagementScreen(ManagementScreens.Initial);
|
setManagementScreen(ManagementScreens.Initial);
|
||||||
break;
|
break;
|
||||||
case ManagementScreens.GroupUserPermissionsCreate:
|
case ManagementScreens.GroupUserPermissionsCreate:
|
||||||
@ -142,7 +147,9 @@ const RightColumn: FC<StateProps> = ({
|
|||||||
setManagementScreen(ManagementScreens.ChatAdministrators);
|
setManagementScreen(ManagementScreens.ChatAdministrators);
|
||||||
break;
|
break;
|
||||||
case ManagementScreens.EditInvite:
|
case ManagementScreens.EditInvite:
|
||||||
|
case ManagementScreens.InviteInfo:
|
||||||
setManagementScreen(ManagementScreens.Invites);
|
setManagementScreen(ManagementScreens.Invites);
|
||||||
|
setOpenedInviteInfo({ invite: undefined });
|
||||||
setEditingExportedInvite({ chatId, invite: undefined });
|
setEditingExportedInvite({ chatId, invite: undefined });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -170,7 +177,7 @@ const RightColumn: FC<StateProps> = ({
|
|||||||
}, [
|
}, [
|
||||||
contentKey, isScrolledDown, toggleChatInfo, openUserInfo, closePollResults, setNewChatMembersDialogState,
|
contentKey, isScrolledDown, toggleChatInfo, openUserInfo, closePollResults, setNewChatMembersDialogState,
|
||||||
managementScreen, toggleManagement, closeLocalTextSearch, setStickerSearchQuery, setGifSearchQuery,
|
managementScreen, toggleManagement, closeLocalTextSearch, setStickerSearchQuery, setGifSearchQuery,
|
||||||
setEditingExportedInvite, chatId,
|
setEditingExportedInvite, chatId, setOpenedInviteInfo,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const handleSelectChatMember = useCallback((memberId, isPromoted) => {
|
const handleSelectChatMember = useCallback((memberId, isPromoted) => {
|
||||||
@ -190,6 +197,13 @@ const RightColumn: FC<StateProps> = ({
|
|||||||
}, COLUMN_CLOSE_DELAY_MS);
|
}, COLUMN_CLOSE_DELAY_MS);
|
||||||
}, [isOpen]);
|
}, [isOpen]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (nextManagementScreen) {
|
||||||
|
setManagementScreen(nextManagementScreen);
|
||||||
|
requestNextManagementScreen(undefined);
|
||||||
|
}
|
||||||
|
}, [nextManagementScreen, requestNextManagementScreen]);
|
||||||
|
|
||||||
// Close Right Column when it transforms into overlayed state on screen resize
|
// Close Right Column when it transforms into overlayed state on screen resize
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen && isOverlaying) {
|
if (isOpen && isOverlaying) {
|
||||||
@ -290,6 +304,7 @@ const RightColumn: FC<StateProps> = ({
|
|||||||
managementScreen={managementScreen}
|
managementScreen={managementScreen}
|
||||||
onClose={close}
|
onClose={close}
|
||||||
shouldSkipAnimation={shouldSkipTransition || shouldSkipHistoryAnimations}
|
shouldSkipAnimation={shouldSkipTransition || shouldSkipHistoryAnimations}
|
||||||
|
onScreenSelect={setManagementScreen}
|
||||||
/>
|
/>
|
||||||
<Transition
|
<Transition
|
||||||
name={(shouldSkipTransition || shouldSkipHistoryAnimations) ? 'none' : 'zoom-fade'}
|
name={(shouldSkipTransition || shouldSkipHistoryAnimations) ? 'none' : 'zoom-fade'}
|
||||||
@ -309,6 +324,7 @@ export default memo(withGlobal(
|
|||||||
(global): StateProps => {
|
(global): StateProps => {
|
||||||
const { chatId, threadId } = selectCurrentMessageList(global) || {};
|
const { chatId, threadId } = selectCurrentMessageList(global) || {};
|
||||||
const areActiveChatsLoaded = selectAreActiveChatsLoaded(global);
|
const areActiveChatsLoaded = selectAreActiveChatsLoaded(global);
|
||||||
|
const nextManagementScreen = chatId ? global.management.byChatId[chatId]?.nextScreen : undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
contentKey: selectRightColumnContentKey(global),
|
contentKey: selectRightColumnContentKey(global),
|
||||||
@ -317,6 +333,7 @@ export default memo(withGlobal(
|
|||||||
currentProfileUserId: global.users.selectedId,
|
currentProfileUserId: global.users.selectedId,
|
||||||
isChatSelected: Boolean(chatId && areActiveChatsLoaded),
|
isChatSelected: Boolean(chatId && areActiveChatsLoaded),
|
||||||
shouldSkipHistoryAnimations: global.shouldSkipHistoryAnimations,
|
shouldSkipHistoryAnimations: global.shouldSkipHistoryAnimations,
|
||||||
|
nextManagementScreen,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(RightColumn));
|
)(RightColumn));
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import React, {
|
|||||||
import { getDispatch, withGlobal } from '../../lib/teact/teactn';
|
import { getDispatch, withGlobal } from '../../lib/teact/teactn';
|
||||||
|
|
||||||
import { ManagementScreens, ProfileState } from '../../types';
|
import { ManagementScreens, ProfileState } from '../../types';
|
||||||
|
import { ApiExportedInvite } from '../../api/types';
|
||||||
|
|
||||||
import { IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment';
|
import { IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment';
|
||||||
import { debounce } from '../../util/schedulers';
|
import { debounce } from '../../util/schedulers';
|
||||||
@ -42,6 +43,7 @@ type OwnProps = {
|
|||||||
profileState?: ProfileState;
|
profileState?: ProfileState;
|
||||||
managementScreen?: ManagementScreens;
|
managementScreen?: ManagementScreens;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
|
onScreenSelect: (screen: ManagementScreens) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
@ -53,6 +55,7 @@ type StateProps = {
|
|||||||
stickerSearchQuery?: string;
|
stickerSearchQuery?: string;
|
||||||
gifSearchQuery?: string;
|
gifSearchQuery?: string;
|
||||||
isEditingInvite?: boolean;
|
isEditingInvite?: boolean;
|
||||||
|
currentInviteInfo?: ApiExportedInvite;
|
||||||
};
|
};
|
||||||
|
|
||||||
const COLUMN_CLOSE_DELAY_MS = 300;
|
const COLUMN_CLOSE_DELAY_MS = 300;
|
||||||
@ -85,9 +88,12 @@ enum HeaderContent {
|
|||||||
ManageInvites,
|
ManageInvites,
|
||||||
ManageEditInvite,
|
ManageEditInvite,
|
||||||
ManageReactions,
|
ManageReactions,
|
||||||
|
ManageInviteInfo,
|
||||||
|
ManageJoinRequests,
|
||||||
}
|
}
|
||||||
|
|
||||||
const RightHeader: FC<OwnProps & StateProps> = ({
|
const RightHeader: FC<OwnProps & StateProps> = ({
|
||||||
|
chatId,
|
||||||
isColumnOpen,
|
isColumnOpen,
|
||||||
isProfile,
|
isProfile,
|
||||||
isSearch,
|
isSearch,
|
||||||
@ -103,11 +109,13 @@ const RightHeader: FC<OwnProps & StateProps> = ({
|
|||||||
canManage,
|
canManage,
|
||||||
isChannel,
|
isChannel,
|
||||||
onClose,
|
onClose,
|
||||||
|
onScreenSelect,
|
||||||
messageSearchQuery,
|
messageSearchQuery,
|
||||||
stickerSearchQuery,
|
stickerSearchQuery,
|
||||||
gifSearchQuery,
|
gifSearchQuery,
|
||||||
shouldSkipAnimation,
|
shouldSkipAnimation,
|
||||||
isEditingInvite,
|
isEditingInvite,
|
||||||
|
currentInviteInfo,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
setLocalTextSearchQuery,
|
setLocalTextSearchQuery,
|
||||||
@ -115,12 +123,25 @@ const RightHeader: FC<OwnProps & StateProps> = ({
|
|||||||
setGifSearchQuery,
|
setGifSearchQuery,
|
||||||
searchTextMessagesLocal,
|
searchTextMessagesLocal,
|
||||||
toggleManagement,
|
toggleManagement,
|
||||||
openHistoryCalendar, addContact,
|
openHistoryCalendar,
|
||||||
|
addContact,
|
||||||
|
setEditingExportedInvite,
|
||||||
|
deleteExportedChatInvite,
|
||||||
} = getDispatch();
|
} = getDispatch();
|
||||||
|
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const backButtonRef = useRef<HTMLDivElement>(null);
|
const backButtonRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const handleEditInviteClick = useCallback(() => {
|
||||||
|
setEditingExportedInvite({ chatId: chatId!, invite: currentInviteInfo! });
|
||||||
|
onScreenSelect(ManagementScreens.EditInvite);
|
||||||
|
}, [chatId, currentInviteInfo, onScreenSelect, setEditingExportedInvite]);
|
||||||
|
|
||||||
|
const handleDeleteInviteClick = useCallback(() => {
|
||||||
|
deleteExportedChatInvite({ chatId: chatId!, link: currentInviteInfo!.link });
|
||||||
|
onScreenSelect(ManagementScreens.Invites);
|
||||||
|
}, [chatId, currentInviteInfo, deleteExportedChatInvite, onScreenSelect]);
|
||||||
|
|
||||||
const handleMessageSearchQueryChange = useCallback((query: string) => {
|
const handleMessageSearchQueryChange = useCallback((query: string) => {
|
||||||
setLocalTextSearchQuery({ query });
|
setLocalTextSearchQuery({ query });
|
||||||
|
|
||||||
@ -203,6 +224,10 @@ const RightHeader: FC<OwnProps & StateProps> = ({
|
|||||||
HeaderContent.ManageGroupAddAdmins
|
HeaderContent.ManageGroupAddAdmins
|
||||||
) : managementScreen === ManagementScreens.Reactions ? (
|
) : managementScreen === ManagementScreens.Reactions ? (
|
||||||
HeaderContent.ManageReactions
|
HeaderContent.ManageReactions
|
||||||
|
) : managementScreen === ManagementScreens.InviteInfo ? (
|
||||||
|
HeaderContent.ManageInviteInfo
|
||||||
|
) : managementScreen === ManagementScreens.JoinRequests ? (
|
||||||
|
HeaderContent.ManageJoinRequests
|
||||||
) : undefined // Never reached
|
) : undefined // Never reached
|
||||||
) : undefined; // When column is closed
|
) : undefined; // When column is closed
|
||||||
|
|
||||||
@ -263,6 +288,38 @@ const RightHeader: FC<OwnProps & StateProps> = ({
|
|||||||
return <h3>{lang('lng_group_invite_title')}</h3>;
|
return <h3>{lang('lng_group_invite_title')}</h3>;
|
||||||
case HeaderContent.ManageEditInvite:
|
case HeaderContent.ManageEditInvite:
|
||||||
return <h3>{isEditingInvite ? lang('EditLink') : lang('NewLink')}</h3>;
|
return <h3>{isEditingInvite ? lang('EditLink') : lang('NewLink')}</h3>;
|
||||||
|
case HeaderContent.ManageInviteInfo:
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h3>{lang('InviteLink')}</h3>
|
||||||
|
<section className="tools">
|
||||||
|
{currentInviteInfo && !currentInviteInfo.isRevoked && (
|
||||||
|
<Button
|
||||||
|
round
|
||||||
|
color="translucent"
|
||||||
|
size="smaller"
|
||||||
|
ariaLabel={lang('Edit')}
|
||||||
|
onClick={handleEditInviteClick}
|
||||||
|
>
|
||||||
|
<i className="icon-edit" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{currentInviteInfo && currentInviteInfo.isRevoked && (
|
||||||
|
<Button
|
||||||
|
round
|
||||||
|
color="danger"
|
||||||
|
size="smaller"
|
||||||
|
ariaLabel={lang('Delete')}
|
||||||
|
onClick={handleDeleteInviteClick}
|
||||||
|
>
|
||||||
|
<i className="icon-delete" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
case HeaderContent.ManageJoinRequests:
|
||||||
|
return <h3>{isChannel ? lang('SubscribeRequests') : lang('MemberRequests')}</h3>;
|
||||||
case HeaderContent.ManageGroupAddAdmins:
|
case HeaderContent.ManageGroupAddAdmins:
|
||||||
return <h3>{lang('Channel.Management.AddModerator')}</h3>;
|
return <h3>{lang('Channel.Management.AddModerator')}</h3>;
|
||||||
case HeaderContent.StickerSearch:
|
case HeaderContent.StickerSearch:
|
||||||
@ -381,6 +438,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
&& (isUserId(chat.id) || ((isChatAdmin(chat) || chat.isCreator) && !chat.isNotJoined)),
|
&& (isUserId(chat.id) || ((isChatAdmin(chat) || chat.isCreator) && !chat.isNotJoined)),
|
||||||
);
|
);
|
||||||
const isEditingInvite = Boolean(chatId && global.management.byChatId[chatId]?.editingInvite);
|
const isEditingInvite = Boolean(chatId && global.management.byChatId[chatId]?.editingInvite);
|
||||||
|
const currentInviteInfo = chatId ? global.management.byChatId[chatId]?.inviteInfo?.invite : undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
canManage,
|
canManage,
|
||||||
@ -391,6 +449,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
stickerSearchQuery,
|
stickerSearchQuery,
|
||||||
gifSearchQuery,
|
gifSearchQuery,
|
||||||
isEditingInvite,
|
isEditingInvite,
|
||||||
|
currentInviteInfo,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(RightHeader));
|
)(RightHeader));
|
||||||
|
|||||||
55
src/components/right/management/JoinRequest.scss
Normal file
55
src/components/right/management/JoinRequest.scss
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
.JoinRequest {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 1rem 0;
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
border-bottom: 1px solid var(--color-borders);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__top {
|
||||||
|
display: flex;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-chat-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__user {
|
||||||
|
display: flex;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__user-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__user-subtitle {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__date {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
margin-left: 1rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
margin-top: 1rem;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__button {
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
101
src/components/right/management/JoinRequest.tsx
Normal file
101
src/components/right/management/JoinRequest.tsx
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
import React, { FC, memo, useCallback } from '../../../lib/teact/teact';
|
||||||
|
import { getDispatch, withGlobal } from '../../../lib/teact/teactn';
|
||||||
|
|
||||||
|
import { ApiUser } from '../../../api/types';
|
||||||
|
|
||||||
|
import useLang from '../../../hooks/useLang';
|
||||||
|
import { getUserFullName } from '../../../modules/helpers';
|
||||||
|
import { selectUser } from '../../../modules/selectors';
|
||||||
|
import { formatHumanDate, formatTime, isToday } from '../../../util/dateFormat';
|
||||||
|
import { getServerTime } from '../../../util/serverTime';
|
||||||
|
import { createClassNameBuilder } from '../../../util/buildClassName';
|
||||||
|
|
||||||
|
import Avatar from '../../common/Avatar';
|
||||||
|
import Button from '../../ui/Button';
|
||||||
|
|
||||||
|
import './JoinRequest.scss';
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
userId: string;
|
||||||
|
about?: string;
|
||||||
|
isChannel?: boolean;
|
||||||
|
date: number;
|
||||||
|
chatId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type StateProps = {
|
||||||
|
user?: ApiUser;
|
||||||
|
isSavedMessages?: boolean;
|
||||||
|
serverTimeOffset: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const JoinRequest: FC<OwnProps & StateProps> = ({
|
||||||
|
userId,
|
||||||
|
about,
|
||||||
|
date,
|
||||||
|
isChannel,
|
||||||
|
user,
|
||||||
|
serverTimeOffset,
|
||||||
|
chatId,
|
||||||
|
}) => {
|
||||||
|
const { openUserInfo, hideChatJoinRequest } = getDispatch();
|
||||||
|
|
||||||
|
const buildClassName = createClassNameBuilder('JoinRequest');
|
||||||
|
const lang = useLang();
|
||||||
|
|
||||||
|
const fullName = getUserFullName(user);
|
||||||
|
const fixedDate = (date - getServerTime(serverTimeOffset)) * 1000 + Date.now();
|
||||||
|
|
||||||
|
const dateString = isToday(new Date(fixedDate))
|
||||||
|
? formatTime(lang, fixedDate) : formatHumanDate(lang, fixedDate, true, false, true);
|
||||||
|
|
||||||
|
const handleUserClick = () => {
|
||||||
|
openUserInfo({ userId });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAcceptRequest = useCallback(() => {
|
||||||
|
hideChatJoinRequest({ chatId, userId, isApproved: true });
|
||||||
|
}, [chatId, hideChatJoinRequest, userId]);
|
||||||
|
|
||||||
|
const handleRejectRequest = useCallback(() => {
|
||||||
|
hideChatJoinRequest({ chatId, userId, isApproved: false });
|
||||||
|
}, [chatId, hideChatJoinRequest, userId]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={buildClassName('&')}>
|
||||||
|
<div className={buildClassName('top')}>
|
||||||
|
<div className={buildClassName('user')} onClick={handleUserClick}>
|
||||||
|
<Avatar
|
||||||
|
key={userId}
|
||||||
|
size="medium"
|
||||||
|
user={user}
|
||||||
|
/>
|
||||||
|
<div className={buildClassName('user-info')}>
|
||||||
|
<div className={buildClassName('user-name')}>{fullName}</div>
|
||||||
|
<div className={buildClassName('user-subtitle')}>{about}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={buildClassName('date')}>{dateString}</div>
|
||||||
|
</div>
|
||||||
|
<div className={buildClassName('buttons')}>
|
||||||
|
<Button className={buildClassName('button')} onClick={handleAcceptRequest}>
|
||||||
|
{isChannel ? lang('ChannelAddToChannel') : lang('ChannelAddToGroup')}
|
||||||
|
</Button>
|
||||||
|
<Button className={buildClassName('button')} isText onClick={handleRejectRequest}>
|
||||||
|
{lang('DismissRequest')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(withGlobal<OwnProps>(
|
||||||
|
(global, { userId }): StateProps => {
|
||||||
|
const user = selectUser(global, userId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
user,
|
||||||
|
serverTimeOffset: global.serverTimeOffset,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
)(JoinRequest));
|
||||||
@ -67,6 +67,7 @@ const ManageChannel: FC<OwnProps & StateProps> = ({
|
|||||||
deleteChannel,
|
deleteChannel,
|
||||||
openChat,
|
openChat,
|
||||||
loadExportedChatInvites,
|
loadExportedChatInvites,
|
||||||
|
loadChatJoinRequests,
|
||||||
} = getDispatch();
|
} = getDispatch();
|
||||||
|
|
||||||
const currentTitle = chat ? (chat.title || '') : '';
|
const currentTitle = chat ? (chat.title || '') : '';
|
||||||
@ -88,8 +89,10 @@ const ManageChannel: FC<OwnProps & StateProps> = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (lastSyncTime) {
|
if (lastSyncTime) {
|
||||||
loadExportedChatInvites({ chatId });
|
loadExportedChatInvites({ chatId });
|
||||||
|
loadExportedChatInvites({ chatId, isRevoked: true });
|
||||||
|
loadChatJoinRequests({ chatId });
|
||||||
}
|
}
|
||||||
}, [chatId, loadExportedChatInvites, lastSyncTime]);
|
}, [chatId, loadExportedChatInvites, lastSyncTime, loadChatJoinRequests]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (progress === ManagementProgress.Complete) {
|
if (progress === ManagementProgress.Complete) {
|
||||||
@ -116,9 +119,13 @@ const ManageChannel: FC<OwnProps & StateProps> = ({
|
|||||||
onScreenSelect(ManagementScreens.ChatAdministrators);
|
onScreenSelect(ManagementScreens.ChatAdministrators);
|
||||||
}, [onScreenSelect]);
|
}, [onScreenSelect]);
|
||||||
|
|
||||||
const handleClickInvites = useCallback(() => {
|
const handleClickInvites = () => {
|
||||||
onScreenSelect(ManagementScreens.Invites);
|
onScreenSelect(ManagementScreens.Invites);
|
||||||
}, [onScreenSelect]);
|
};
|
||||||
|
|
||||||
|
const handleClickRequests = () => {
|
||||||
|
onScreenSelect(ManagementScreens.JoinRequests);
|
||||||
|
};
|
||||||
|
|
||||||
const handleSetPhoto = useCallback((file: File) => {
|
const handleSetPhoto = useCallback((file: File) => {
|
||||||
setPhoto(file);
|
setPhoto(file);
|
||||||
@ -241,6 +248,18 @@ const ManageChannel: FC<OwnProps & StateProps> = ({
|
|||||||
</span>
|
</span>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
)}
|
)}
|
||||||
|
{Boolean(chat.joinRequests?.length) && (
|
||||||
|
<ListItem
|
||||||
|
icon="add-user-filled"
|
||||||
|
onClick={handleClickRequests}
|
||||||
|
multiline
|
||||||
|
>
|
||||||
|
<span className="title">{lang('SubscribeRequests')}</span>
|
||||||
|
<span className="subtitle">
|
||||||
|
{formatInteger(chat.joinRequests!.length)}
|
||||||
|
</span>
|
||||||
|
</ListItem>
|
||||||
|
)}
|
||||||
<ListItem
|
<ListItem
|
||||||
icon="reactions"
|
icon="reactions"
|
||||||
multiline
|
multiline
|
||||||
|
|||||||
@ -79,6 +79,7 @@ const ManageGroup: FC<OwnProps & StateProps> = ({
|
|||||||
closeManagement,
|
closeManagement,
|
||||||
openChat,
|
openChat,
|
||||||
loadExportedChatInvites,
|
loadExportedChatInvites,
|
||||||
|
loadChatJoinRequests,
|
||||||
} = getDispatch();
|
} = getDispatch();
|
||||||
|
|
||||||
const [isDeleteDialogOpen, openDeleteDialog, closeDeleteDialog] = useFlag();
|
const [isDeleteDialogOpen, openDeleteDialog, closeDeleteDialog] = useFlag();
|
||||||
@ -99,8 +100,10 @@ const ManageGroup: FC<OwnProps & StateProps> = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (lastSyncTime && canInvite) {
|
if (lastSyncTime && canInvite) {
|
||||||
loadExportedChatInvites({ chatId });
|
loadExportedChatInvites({ chatId });
|
||||||
|
loadExportedChatInvites({ chatId, isRevoked: true });
|
||||||
|
loadChatJoinRequests({ chatId });
|
||||||
}
|
}
|
||||||
}, [chatId, loadExportedChatInvites, lastSyncTime, canInvite]);
|
}, [chatId, loadExportedChatInvites, lastSyncTime, canInvite, loadChatJoinRequests]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (progress === ManagementProgress.Complete) {
|
if (progress === ManagementProgress.Complete) {
|
||||||
@ -129,9 +132,13 @@ const ManageGroup: FC<OwnProps & StateProps> = ({
|
|||||||
onScreenSelect(ManagementScreens.ChatAdministrators);
|
onScreenSelect(ManagementScreens.ChatAdministrators);
|
||||||
}, [onScreenSelect]);
|
}, [onScreenSelect]);
|
||||||
|
|
||||||
const handleClickInvites = useCallback(() => {
|
const handleClickInvites = () => {
|
||||||
onScreenSelect(ManagementScreens.Invites);
|
onScreenSelect(ManagementScreens.Invites);
|
||||||
}, [onScreenSelect]);
|
};
|
||||||
|
|
||||||
|
const handleClickRequests = () => {
|
||||||
|
onScreenSelect(ManagementScreens.JoinRequests);
|
||||||
|
};
|
||||||
|
|
||||||
const handleSetPhoto = useCallback((file: File) => {
|
const handleSetPhoto = useCallback((file: File) => {
|
||||||
setPhoto(file);
|
setPhoto(file);
|
||||||
@ -317,6 +324,18 @@ const ManageGroup: FC<OwnProps & StateProps> = ({
|
|||||||
</span>
|
</span>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
)}
|
)}
|
||||||
|
{Boolean(chat.joinRequests?.length) && (
|
||||||
|
<ListItem
|
||||||
|
icon="add-user-filled"
|
||||||
|
onClick={handleClickRequests}
|
||||||
|
multiline
|
||||||
|
>
|
||||||
|
<span className="title">{lang('MemberRequests')}</span>
|
||||||
|
<span className="subtitle">
|
||||||
|
{formatInteger(chat.joinRequests!.length)}
|
||||||
|
</span>
|
||||||
|
</ListItem>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="section">
|
<div className="section">
|
||||||
<ListItem icon="group" multiline onClick={handleClickMembers}>
|
<ListItem icon="group" multiline onClick={handleClickMembers}>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { ChangeEvent } from 'react';
|
import { ChangeEvent } from 'react';
|
||||||
import React, {
|
import React, {
|
||||||
FC, memo, useCallback, useEffect, useState,
|
FC, memo, useCallback, useState,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { getDispatch, withGlobal } from '../../../lib/teact/teactn';
|
import { getDispatch, withGlobal } from '../../../lib/teact/teactn';
|
||||||
|
|
||||||
@ -18,6 +18,7 @@ import InputText from '../../ui/InputText';
|
|||||||
import RadioGroup from '../../ui/RadioGroup';
|
import RadioGroup from '../../ui/RadioGroup';
|
||||||
import Button from '../../ui/Button';
|
import Button from '../../ui/Button';
|
||||||
import FloatingActionButton from '../../ui/FloatingActionButton';
|
import FloatingActionButton from '../../ui/FloatingActionButton';
|
||||||
|
import useOnChange from '../../../hooks/useOnChange';
|
||||||
import CalendarModal from '../../common/CalendarModal';
|
import CalendarModal from '../../common/CalendarModal';
|
||||||
|
|
||||||
const DEFAULT_USAGE_LIMITS = [1, 10, 100];
|
const DEFAULT_USAGE_LIMITS = [1, 10, 100];
|
||||||
@ -61,7 +62,8 @@ const ManageInvite: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
useHistoryBack(isActive, onClose);
|
useHistoryBack(isActive, onClose);
|
||||||
|
|
||||||
useEffect(() => {
|
useOnChange(([oldEditingInvite]) => {
|
||||||
|
if (oldEditingInvite === editingInvite) return;
|
||||||
if (!editingInvite) {
|
if (!editingInvite) {
|
||||||
setTitle('');
|
setTitle('');
|
||||||
setSelectedExpireOption('unlimited');
|
setSelectedExpireOption('unlimited');
|
||||||
|
|||||||
152
src/components/right/management/ManageInviteInfo.tsx
Normal file
152
src/components/right/management/ManageInviteInfo.tsx
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
import React, {
|
||||||
|
FC, memo, useCallback, useEffect,
|
||||||
|
} from '../../../lib/teact/teact';
|
||||||
|
import { getDispatch, withGlobal } from '../../../lib/teact/teactn';
|
||||||
|
|
||||||
|
import { ApiChatInviteImporter, ApiExportedInvite, ApiUser } from '../../../api/types';
|
||||||
|
|
||||||
|
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||||
|
import useLang from '../../../hooks/useLang';
|
||||||
|
import { copyTextToClipboard } from '../../../util/clipboard';
|
||||||
|
import { getServerTime } from '../../../util/serverTime';
|
||||||
|
import { formatFullDate, formatMediaDateTime, formatTime } from '../../../util/dateFormat';
|
||||||
|
|
||||||
|
import PrivateChatInfo from '../../common/PrivateChatInfo';
|
||||||
|
import ListItem from '../../ui/ListItem';
|
||||||
|
import Button from '../../ui/Button';
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
chatId: string;
|
||||||
|
onClose: NoneToVoidFunction;
|
||||||
|
isActive: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type StateProps = {
|
||||||
|
invite?: ApiExportedInvite;
|
||||||
|
importers?: ApiChatInviteImporter[];
|
||||||
|
admin?: ApiUser;
|
||||||
|
serverTimeOffset: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ManageInviteInfo: FC<OwnProps & StateProps> = ({
|
||||||
|
chatId,
|
||||||
|
invite,
|
||||||
|
importers,
|
||||||
|
isActive,
|
||||||
|
serverTimeOffset,
|
||||||
|
onClose,
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
showNotification,
|
||||||
|
loadChatInviteImporters,
|
||||||
|
openUserInfo,
|
||||||
|
} = getDispatch();
|
||||||
|
|
||||||
|
const lang = useLang();
|
||||||
|
const {
|
||||||
|
usage = 0, usageLimit, link, adminId,
|
||||||
|
} = invite || {};
|
||||||
|
const expireDate = invite?.expireDate && (invite.expireDate - getServerTime(serverTimeOffset)) * 1000 + Date.now();
|
||||||
|
const isExpired = ((invite?.expireDate || 0) - getServerTime(serverTimeOffset)) < 0;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (link) loadChatInviteImporters({ chatId, link });
|
||||||
|
}, [chatId, link, loadChatInviteImporters]);
|
||||||
|
|
||||||
|
const handleCopyClicked = useCallback(() => {
|
||||||
|
copyTextToClipboard(invite!.link);
|
||||||
|
showNotification({
|
||||||
|
message: lang('LinkCopied'),
|
||||||
|
});
|
||||||
|
}, [invite, lang, showNotification]);
|
||||||
|
|
||||||
|
useHistoryBack(isActive, onClose);
|
||||||
|
|
||||||
|
const renderImporters = () => {
|
||||||
|
if (invite?.isRevoked) return undefined;
|
||||||
|
if (!importers) return <p className="text-muted">{lang('Loading')}</p>;
|
||||||
|
return (
|
||||||
|
<div className="section">
|
||||||
|
<p>{importers.length ? lang('PeopleJoined', usage) : lang('NoOneJoined')}</p>
|
||||||
|
<p className="text-muted">
|
||||||
|
{!importers.length && (
|
||||||
|
usageLimit ? lang('PeopleCanJoinViaLinkCount', usageLimit - usage) : lang('NoOneJoinedYet')
|
||||||
|
)}
|
||||||
|
{importers.map((importer) => (
|
||||||
|
<ListItem
|
||||||
|
className="chat-item-clickable scroll-item small-icon"
|
||||||
|
onClick={() => openUserInfo({ id: importer.userId })}
|
||||||
|
>
|
||||||
|
<PrivateChatInfo
|
||||||
|
userId={importer.userId}
|
||||||
|
status={formatMediaDateTime(lang, importer.date * 1000, true)}
|
||||||
|
forceShowSelf
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
))}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="Management ManageInviteInfo">
|
||||||
|
<div className="custom-scroll">
|
||||||
|
{!invite && (
|
||||||
|
<p className="text-muted">{lang('Loading')}</p>
|
||||||
|
)}
|
||||||
|
{invite && (
|
||||||
|
<>
|
||||||
|
<div className="section">
|
||||||
|
<h3>{invite.title || invite.link}</h3>
|
||||||
|
<input
|
||||||
|
className="form-control"
|
||||||
|
value={invite.link}
|
||||||
|
readOnly
|
||||||
|
onClick={handleCopyClicked}
|
||||||
|
/>
|
||||||
|
<Button className="copy-link" onClick={handleCopyClicked}>{lang('CopyLink')}</Button>
|
||||||
|
{expireDate && (
|
||||||
|
<p className="text-muted">
|
||||||
|
{isExpired
|
||||||
|
? lang('ExpiredLink')
|
||||||
|
: lang('LinkExpiresIn', `${formatFullDate(lang, expireDate)} ${formatTime(lang, expireDate)}`)}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{adminId && (
|
||||||
|
<div className="section">
|
||||||
|
<p>{lang('LinkCreatedeBy')}</p>
|
||||||
|
<ListItem
|
||||||
|
className="chat-item-clickable scroll-item small-icon"
|
||||||
|
onClick={() => openUserInfo({ id: adminId })}
|
||||||
|
>
|
||||||
|
<PrivateChatInfo
|
||||||
|
userId={adminId}
|
||||||
|
status={formatMediaDateTime(lang, invite.date * 1000, true)}
|
||||||
|
forceShowSelf
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{renderImporters()}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(withGlobal<OwnProps>(
|
||||||
|
(global, { chatId }): StateProps => {
|
||||||
|
const { inviteInfo } = global.management.byChatId[chatId];
|
||||||
|
const invite = inviteInfo?.invite;
|
||||||
|
const importers = inviteInfo?.importers;
|
||||||
|
|
||||||
|
return {
|
||||||
|
invite,
|
||||||
|
importers,
|
||||||
|
serverTimeOffset: global.serverTimeOffset,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
)(ManageInviteInfo));
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import React, {
|
import React, {
|
||||||
FC, memo, useCallback, useMemo,
|
FC, memo, useCallback, useMemo, useState,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { getDispatch, withGlobal } from '../../../lib/teact/teactn';
|
import { getDispatch, withGlobal } from '../../../lib/teact/teactn';
|
||||||
|
|
||||||
@ -15,12 +15,14 @@ import { selectChat } from '../../../modules/selectors';
|
|||||||
import { copyTextToClipboard } from '../../../util/clipboard';
|
import { copyTextToClipboard } from '../../../util/clipboard';
|
||||||
import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment';
|
import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment';
|
||||||
import { getServerTime } from '../../../util/serverTime';
|
import { getServerTime } from '../../../util/serverTime';
|
||||||
|
import useFlag from '../../../hooks/useFlag';
|
||||||
|
|
||||||
import ListItem from '../../ui/ListItem';
|
import ListItem from '../../ui/ListItem';
|
||||||
import NothingFound from '../../common/NothingFound';
|
import NothingFound from '../../common/NothingFound';
|
||||||
import Button from '../../ui/Button';
|
import Button from '../../ui/Button';
|
||||||
import DropdownMenu from '../../ui/DropdownMenu';
|
import DropdownMenu from '../../ui/DropdownMenu';
|
||||||
import MenuItem from '../../ui/MenuItem';
|
import MenuItem from '../../ui/MenuItem';
|
||||||
|
import ConfirmDialog from '../../ui/ConfirmDialog';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
@ -32,6 +34,7 @@ type OwnProps = {
|
|||||||
type StateProps = {
|
type StateProps = {
|
||||||
chat?: ApiChat;
|
chat?: ApiChat;
|
||||||
exportedInvites?: ApiExportedInvite[];
|
exportedInvites?: ApiExportedInvite[];
|
||||||
|
revokedExportedInvites?: ApiExportedInvite[];
|
||||||
serverTimeOffset: number;
|
serverTimeOffset: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -49,12 +52,26 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
|
|||||||
chatId,
|
chatId,
|
||||||
chat,
|
chat,
|
||||||
exportedInvites,
|
exportedInvites,
|
||||||
|
revokedExportedInvites,
|
||||||
isActive,
|
isActive,
|
||||||
serverTimeOffset,
|
serverTimeOffset,
|
||||||
onClose,
|
onClose,
|
||||||
onScreenSelect,
|
onScreenSelect,
|
||||||
}) => {
|
}) => {
|
||||||
const { setEditingExportedInvite, showNotification, editExportedChatInvite } = getDispatch();
|
const {
|
||||||
|
setEditingExportedInvite,
|
||||||
|
showNotification,
|
||||||
|
editExportedChatInvite,
|
||||||
|
deleteExportedChatInvite,
|
||||||
|
deleteRevokedExportedChatInvites,
|
||||||
|
setOpenedInviteInfo,
|
||||||
|
} = getDispatch();
|
||||||
|
const [isDeleteRevokeAllDialogOpen, openDeleteRevokeAllDialog, closeDeleteRevokeAllDialog] = useFlag();
|
||||||
|
const [isRevokeDialogOpen, openRevokeDialog, closeRevokeDialog] = useFlag();
|
||||||
|
const [revokingInvite, setRevokingInvite] = useState<ApiExportedInvite | undefined>();
|
||||||
|
const [isDeleteDialogOpen, openDeleteDialog, closeDeleteDialog] = useFlag();
|
||||||
|
const [deletingInvite, setDeletingInvite] = useState<ApiExportedInvite | undefined>();
|
||||||
|
|
||||||
useHistoryBack(isActive, onClose);
|
useHistoryBack(isActive, onClose);
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
@ -74,8 +91,7 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
|
|||||||
const primaryInviteLink = chat?.username ? `t.me/${chat.username}` : primaryInvite?.link;
|
const primaryInviteLink = chat?.username ? `t.me/${chat.username}` : primaryInvite?.link;
|
||||||
const temporalInvites = useMemo(() => {
|
const temporalInvites = useMemo(() => {
|
||||||
const invites = chat?.username ? exportedInvites : exportedInvites?.filter(({ isPermanent }) => !isPermanent);
|
const invites = chat?.username ? exportedInvites : exportedInvites?.filter(({ isPermanent }) => !isPermanent);
|
||||||
return invites?.filter(({ isRevoked }) => !isRevoked)
|
return invites?.sort(inviteComparator);
|
||||||
.sort(inviteComparator);
|
|
||||||
}, [chat?.username, exportedInvites]);
|
}, [chat?.username, exportedInvites]);
|
||||||
|
|
||||||
const editInvite = (invite: ApiExportedInvite) => {
|
const editInvite = (invite: ApiExportedInvite) => {
|
||||||
@ -98,15 +114,53 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
}, [chatId, editExportedChatInvite]);
|
}, [chatId, editExportedChatInvite]);
|
||||||
|
|
||||||
|
const askToRevoke = useCallback((invite: ApiExportedInvite) => {
|
||||||
|
setRevokingInvite(invite);
|
||||||
|
openRevokeDialog();
|
||||||
|
}, [openRevokeDialog]);
|
||||||
|
|
||||||
|
const handleRevoke = useCallback(() => {
|
||||||
|
if (!revokingInvite) return;
|
||||||
|
revokeInvite(revokingInvite);
|
||||||
|
setRevokingInvite(undefined);
|
||||||
|
closeRevokeDialog();
|
||||||
|
}, [closeRevokeDialog, revokeInvite, revokingInvite]);
|
||||||
|
|
||||||
const handleCreateNewClick = useCallback(() => {
|
const handleCreateNewClick = useCallback(() => {
|
||||||
onScreenSelect(ManagementScreens.EditInvite);
|
onScreenSelect(ManagementScreens.EditInvite);
|
||||||
}, [onScreenSelect]);
|
}, [onScreenSelect]);
|
||||||
|
|
||||||
const handlePrimaryRevoke = useCallback(() => {
|
const handlePrimaryRevoke = useCallback(() => {
|
||||||
if (primaryInvite) {
|
if (primaryInvite) {
|
||||||
revokeInvite(primaryInvite);
|
askToRevoke(primaryInvite);
|
||||||
}
|
}
|
||||||
}, [primaryInvite, revokeInvite]);
|
}, [askToRevoke, primaryInvite]);
|
||||||
|
|
||||||
|
const handleDeleteAllRevoked = useCallback(() => {
|
||||||
|
deleteRevokedExportedChatInvites({ chatId });
|
||||||
|
closeDeleteRevokeAllDialog();
|
||||||
|
}, [chatId, closeDeleteRevokeAllDialog, deleteRevokedExportedChatInvites]);
|
||||||
|
|
||||||
|
const showInviteInfo = useCallback((invite: ApiExportedInvite) => {
|
||||||
|
setOpenedInviteInfo({ chatId, invite });
|
||||||
|
onScreenSelect(ManagementScreens.InviteInfo);
|
||||||
|
}, [chatId, onScreenSelect, setOpenedInviteInfo]);
|
||||||
|
|
||||||
|
const deleteInvite = useCallback((invite: ApiExportedInvite) => {
|
||||||
|
deleteExportedChatInvite({ chatId, link: invite.link });
|
||||||
|
}, [chatId, deleteExportedChatInvite]);
|
||||||
|
|
||||||
|
const askToDelete = useCallback((invite: ApiExportedInvite) => {
|
||||||
|
setDeletingInvite(invite);
|
||||||
|
openDeleteDialog();
|
||||||
|
}, [openDeleteDialog]);
|
||||||
|
|
||||||
|
const handleDelete = useCallback(() => {
|
||||||
|
if (!deletingInvite) return;
|
||||||
|
deleteInvite(deletingInvite);
|
||||||
|
setDeletingInvite(undefined);
|
||||||
|
closeDeleteDialog();
|
||||||
|
}, [closeDeleteDialog, deleteInvite, deletingInvite]);
|
||||||
|
|
||||||
const copyLink = useCallback((link: string) => {
|
const copyLink = useCallback((link: string) => {
|
||||||
copyTextToClipboard(link);
|
copyTextToClipboard(link);
|
||||||
@ -121,10 +175,10 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const prepareUsageText = (invite: ApiExportedInvite) => {
|
const prepareUsageText = (invite: ApiExportedInvite) => {
|
||||||
const {
|
const {
|
||||||
usage = 0, usageLimit, expireDate, isPermanent, requested,
|
usage = 0, usageLimit, expireDate, isPermanent, requested, isRevoked,
|
||||||
} = invite;
|
} = invite;
|
||||||
let text = '';
|
let text = '';
|
||||||
if (usageLimit && usage < usageLimit) {
|
if (!isRevoked && usageLimit && usage < usageLimit) {
|
||||||
text = lang('CanJoin', usageLimit - usage);
|
text = lang('CanJoin', usageLimit - usage);
|
||||||
} else if (usage) {
|
} else if (usage) {
|
||||||
text = lang('PeopleJoined', usage);
|
text = lang('PeopleJoined', usage);
|
||||||
@ -132,6 +186,11 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
|
|||||||
text = lang('NoOneJoined');
|
text = lang('NoOneJoined');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isRevoked) {
|
||||||
|
text += ` ${BULLET} ${lang('Revoked')}`;
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
if (requested) {
|
if (requested) {
|
||||||
text += ` ${BULLET} ${lang('JoinRequests', requested)}`;
|
text += ` ${BULLET} ${lang('JoinRequests', requested)}`;
|
||||||
}
|
}
|
||||||
@ -160,19 +219,30 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
|
|||||||
icon: 'copy',
|
icon: 'copy',
|
||||||
handler: () => copyLink(invite.link),
|
handler: () => copyLink(invite.link),
|
||||||
});
|
});
|
||||||
if (!invite.isPermanent) {
|
|
||||||
|
if (!invite.isPermanent && !invite.isRevoked) {
|
||||||
actions.push({
|
actions.push({
|
||||||
title: lang('Edit'),
|
title: lang('Edit'),
|
||||||
icon: lang('edit'),
|
icon: 'edit',
|
||||||
handler: () => editInvite(invite),
|
handler: () => editInvite(invite),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
actions.push({
|
|
||||||
title: lang('RevokeButton'),
|
if (!invite.isRevoked) {
|
||||||
icon: lang('delete'),
|
actions.push({
|
||||||
handler: () => revokeInvite(invite),
|
title: lang('RevokeButton'),
|
||||||
destructive: true,
|
icon: 'delete',
|
||||||
});
|
handler: () => askToRevoke(invite),
|
||||||
|
destructive: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
actions.push({
|
||||||
|
title: lang('DeleteLink'),
|
||||||
|
icon: 'delete',
|
||||||
|
handler: () => askToDelete(invite),
|
||||||
|
destructive: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
return actions;
|
return actions;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -225,13 +295,13 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
|
|||||||
<Button isText key="create" className="create-link" onClick={handleCreateNewClick}>
|
<Button isText key="create" className="create-link" onClick={handleCreateNewClick}>
|
||||||
{lang('CreateNewLink')}
|
{lang('CreateNewLink')}
|
||||||
</Button>
|
</Button>
|
||||||
{!temporalInvites && <NothingFound text="No links found" key="nothing" />}
|
{(!temporalInvites || !temporalInvites.length) && <NothingFound text="No links found" key="nothing" />}
|
||||||
{temporalInvites?.map((invite) => (
|
{temporalInvites?.map((invite) => (
|
||||||
<ListItem
|
<ListItem
|
||||||
icon="link"
|
icon="link"
|
||||||
secondaryIcon="more"
|
secondaryIcon="more"
|
||||||
multiline
|
multiline
|
||||||
onClick={() => copyLink(invite.link)}
|
onClick={() => showInviteInfo(invite)}
|
||||||
contextActions={prepareContextActions(invite)}
|
contextActions={prepareContextActions(invite)}
|
||||||
key={invite.link}
|
key={invite.link}
|
||||||
>
|
>
|
||||||
@ -243,18 +313,68 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
|
|||||||
))}
|
))}
|
||||||
<p className="text-muted hint" key="links-hint">{lang('ManageLinksInfoHelp')}</p>
|
<p className="text-muted hint" key="links-hint">{lang('ManageLinksInfoHelp')}</p>
|
||||||
</div>
|
</div>
|
||||||
|
{revokedExportedInvites && Boolean(revokedExportedInvites.length) && (
|
||||||
|
<div className="section" teactFastList>
|
||||||
|
<p className="text-muted" key="title">{lang('RevokedLinks')}</p>
|
||||||
|
<ListItem
|
||||||
|
icon="delete"
|
||||||
|
destructive
|
||||||
|
key="delete"
|
||||||
|
onClick={openDeleteRevokeAllDialog}
|
||||||
|
>
|
||||||
|
<span className="title">{lang('DeleteAllRevokedLinks')}</span>
|
||||||
|
</ListItem>
|
||||||
|
{revokedExportedInvites?.map((invite) => (
|
||||||
|
<ListItem
|
||||||
|
icon="link"
|
||||||
|
secondaryIcon="more"
|
||||||
|
multiline
|
||||||
|
onClick={() => showInviteInfo(invite)}
|
||||||
|
contextActions={prepareContextActions(invite)}
|
||||||
|
key={invite.link}
|
||||||
|
>
|
||||||
|
<span className="title">{invite.title || invite.link}</span>
|
||||||
|
<span className="subtitle" dir="auto">
|
||||||
|
{prepareUsageText(invite)}
|
||||||
|
</span>
|
||||||
|
</ListItem>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
<ConfirmDialog
|
||||||
|
isOpen={isDeleteRevokeAllDialogOpen}
|
||||||
|
onClose={closeDeleteRevokeAllDialog}
|
||||||
|
title={lang('DeleteAllRevokedLinks')}
|
||||||
|
text={lang('DeleteAllRevokedLinkHelp')}
|
||||||
|
confirmHandler={handleDeleteAllRevoked}
|
||||||
|
/>
|
||||||
|
<ConfirmDialog
|
||||||
|
isOpen={isRevokeDialogOpen}
|
||||||
|
onClose={closeRevokeDialog}
|
||||||
|
title={lang('RevokeLink')}
|
||||||
|
text={lang('RevokeAlert')}
|
||||||
|
confirmHandler={handleRevoke}
|
||||||
|
/>
|
||||||
|
<ConfirmDialog
|
||||||
|
isOpen={isDeleteDialogOpen}
|
||||||
|
onClose={closeDeleteDialog}
|
||||||
|
title={lang('DeleteLink')}
|
||||||
|
text={lang('DeleteLinkHelp')}
|
||||||
|
confirmHandler={handleDelete}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global, { chatId }): StateProps => {
|
(global, { chatId }): StateProps => {
|
||||||
const { invites } = global.management.byChatId[chatId];
|
const { invites, revokedInvites } = global.management.byChatId[chatId];
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
exportedInvites: invites,
|
exportedInvites: invites,
|
||||||
|
revokedExportedInvites: revokedInvites,
|
||||||
chat,
|
chat,
|
||||||
serverTimeOffset: global.serverTimeOffset,
|
serverTimeOffset: global.serverTimeOffset,
|
||||||
};
|
};
|
||||||
|
|||||||
119
src/components/right/management/ManageJoinRequests.tsx
Normal file
119
src/components/right/management/ManageJoinRequests.tsx
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
import React, {
|
||||||
|
FC, memo, useCallback, useEffect,
|
||||||
|
} from '../../../lib/teact/teact';
|
||||||
|
import { getDispatch, withGlobal } from '../../../lib/teact/teactn';
|
||||||
|
|
||||||
|
import { ApiChat } from '../../../api/types';
|
||||||
|
|
||||||
|
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||||
|
import { selectChat } from '../../../modules/selectors';
|
||||||
|
import { isChatChannel } from '../../../modules/helpers';
|
||||||
|
import useLang from '../../../hooks/useLang';
|
||||||
|
import useFlag from '../../../hooks/useFlag';
|
||||||
|
|
||||||
|
import JoinRequest from './JoinRequest';
|
||||||
|
import Button from '../../ui/Button';
|
||||||
|
import ConfirmDialog from '../../ui/ConfirmDialog';
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
chatId: string;
|
||||||
|
onClose: NoneToVoidFunction;
|
||||||
|
isActive: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type StateProps = {
|
||||||
|
chat?: ApiChat;
|
||||||
|
isChannel?: boolean;
|
||||||
|
serverTimeOffset: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ManageJoinRequests: FC<OwnProps & StateProps> = ({
|
||||||
|
chat,
|
||||||
|
chatId,
|
||||||
|
isActive,
|
||||||
|
isChannel,
|
||||||
|
onClose,
|
||||||
|
}) => {
|
||||||
|
const { hideAllChatJoinRequests, loadChatJoinRequests } = getDispatch();
|
||||||
|
const [isAcceptAllDialogOpen, openAcceptAllDialog, closeAcceptAllDialog] = useFlag();
|
||||||
|
const [isRejectAllDialogOpen, openRejectAllDialog, closeRejectAllDialog] = useFlag();
|
||||||
|
|
||||||
|
const lang = useLang();
|
||||||
|
|
||||||
|
useHistoryBack(isActive, onClose);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!chat?.joinRequests) {
|
||||||
|
loadChatJoinRequests({ chatId });
|
||||||
|
}
|
||||||
|
}, [chat, chatId, loadChatJoinRequests]);
|
||||||
|
|
||||||
|
const handleAcceptAllRequests = useCallback(() => {
|
||||||
|
hideAllChatJoinRequests({ chatId, isApproved: true });
|
||||||
|
closeAcceptAllDialog();
|
||||||
|
}, [hideAllChatJoinRequests, chatId, closeAcceptAllDialog]);
|
||||||
|
|
||||||
|
const handleRejectAllRequests = useCallback(() => {
|
||||||
|
hideAllChatJoinRequests({ chatId, isApproved: false });
|
||||||
|
closeRejectAllDialog();
|
||||||
|
}, [hideAllChatJoinRequests, chatId, closeRejectAllDialog]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="Management ManageJoinRequests">
|
||||||
|
{Boolean(chat?.joinRequests?.length) && (
|
||||||
|
<div className="section bulk-actions">
|
||||||
|
<Button className="bulk-action-button" onClick={openAcceptAllDialog}>Accept all</Button>
|
||||||
|
<Button className="bulk-action-button" onClick={openRejectAllDialog} isText>Dismiss all</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="section">
|
||||||
|
<div className="custom-scroll" teactFastList>
|
||||||
|
<p key="title">
|
||||||
|
{chat?.joinRequests?.length ? lang('JoinRequests', chat?.joinRequests?.length) : lang('NoMemberRequests')}
|
||||||
|
</p>
|
||||||
|
{chat?.joinRequests?.length === 0 && (
|
||||||
|
<p className="text-muted" key="empty">
|
||||||
|
{isChannel ? lang('NoSubscribeRequestsDescription') : lang('NoMemberRequestsDescription')}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{chat?.joinRequests?.map(({ userId, about, date }) => (
|
||||||
|
<JoinRequest
|
||||||
|
userId={userId}
|
||||||
|
about={about}
|
||||||
|
date={date}
|
||||||
|
isChannel={isChannel}
|
||||||
|
chatId={chatId}
|
||||||
|
key={userId}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ConfirmDialog
|
||||||
|
isOpen={isAcceptAllDialogOpen}
|
||||||
|
onClose={closeAcceptAllDialog}
|
||||||
|
title="Accept all requests?"
|
||||||
|
text="Are you sure you want to accept all requests?"
|
||||||
|
confirmHandler={handleAcceptAllRequests}
|
||||||
|
/>
|
||||||
|
<ConfirmDialog
|
||||||
|
isOpen={isRejectAllDialogOpen}
|
||||||
|
onClose={closeRejectAllDialog}
|
||||||
|
title="Reject all requests?"
|
||||||
|
text="Are you sure you want to reject all requests?"
|
||||||
|
confirmHandler={handleRejectAllRequests}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(withGlobal<OwnProps>(
|
||||||
|
(global, { chatId }): StateProps => {
|
||||||
|
const chat = selectChat(global, chatId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
chat,
|
||||||
|
serverTimeOffset: global.serverTimeOffset,
|
||||||
|
isChannel: chat && isChatChannel(chat),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
)(ManageJoinRequests));
|
||||||
@ -207,6 +207,25 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ManageInviteInfo {
|
||||||
|
.copy-link {
|
||||||
|
margin-top: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ManageJoinRequests {
|
||||||
|
.bulk-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-action-button {
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.ManageInvite, .ManageInvites {
|
.ManageInvite, .ManageInvites {
|
||||||
.hint {
|
.hint {
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
|
|||||||
@ -21,6 +21,8 @@ import ManageGroupUserPermissionsCreate from './ManageGroupUserPermissionsCreate
|
|||||||
import ManageInvites from './ManageInvites';
|
import ManageInvites from './ManageInvites';
|
||||||
import ManageInvite from './ManageInvite';
|
import ManageInvite from './ManageInvite';
|
||||||
import ManageReactions from './ManageReactions';
|
import ManageReactions from './ManageReactions';
|
||||||
|
import ManageInviteInfo from './ManageInviteInfo';
|
||||||
|
import ManageJoinRequests from './ManageJoinRequests';
|
||||||
|
|
||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
@ -268,6 +270,22 @@ const Management: FC<OwnProps & StateProps> = ({
|
|||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
case ManagementScreens.InviteInfo:
|
||||||
|
return (
|
||||||
|
<ManageInviteInfo
|
||||||
|
chatId={chatId}
|
||||||
|
isActive={isActive}
|
||||||
|
onClose={onClose}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case ManagementScreens.JoinRequests:
|
||||||
|
return (
|
||||||
|
<ManageJoinRequests
|
||||||
|
chatId={chatId}
|
||||||
|
isActive={isActive}
|
||||||
|
onClose={onClose}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined; // Never reached
|
return undefined; // Never reached
|
||||||
|
|||||||
@ -556,6 +556,8 @@ export type ActionTypes = (
|
|||||||
// management
|
// management
|
||||||
'toggleManagement' | 'closeManagement' | 'checkPublicLink' | 'updatePublicLink' | 'updatePrivateLink' |
|
'toggleManagement' | 'closeManagement' | 'checkPublicLink' | 'updatePublicLink' | 'updatePrivateLink' |
|
||||||
'setEditingExportedInvite' | 'loadExportedChatInvites' | 'editExportedChatInvite' | 'exportChatInvite' |
|
'setEditingExportedInvite' | 'loadExportedChatInvites' | 'editExportedChatInvite' | 'exportChatInvite' |
|
||||||
|
'deleteExportedChatInvite' | 'deleteRevokedExportedChatInvites' | 'setOpenedInviteInfo' | 'loadChatInviteImporters' |
|
||||||
|
'loadChatJoinRequests' | 'hideChatJoinRequest' | 'hideAllChatJoinRequests' | 'requestNextManagementScreen' |
|
||||||
// groups
|
// groups
|
||||||
'togglePreHistoryHidden' | 'updateChatDefaultBannedRights' | 'updateChatMemberBannedRights' | 'updateChatAdmin' |
|
'togglePreHistoryHidden' | 'updateChatDefaultBannedRights' | 'updateChatMemberBannedRights' | 'updateChatAdmin' |
|
||||||
'acceptInviteConfirmation' |
|
'acceptInviteConfirmation' |
|
||||||
|
|||||||
@ -1084,8 +1084,12 @@ messages.unpinAllMessages#f025bc8b peer:InputPeer = messages.AffectedHistory;
|
|||||||
messages.deleteChat#5bd0ee50 chat_id:long = Bool;
|
messages.deleteChat#5bd0ee50 chat_id:long = Bool;
|
||||||
messages.getExportedChatInvites#a2b5a3f6 flags:# revoked:flags.3?true peer:InputPeer admin_id:InputUser offset_date:flags.2?int offset_link:flags.2?string limit:int = messages.ExportedChatInvites;
|
messages.getExportedChatInvites#a2b5a3f6 flags:# revoked:flags.3?true peer:InputPeer admin_id:InputUser offset_date:flags.2?int offset_link:flags.2?string limit:int = messages.ExportedChatInvites;
|
||||||
messages.editExportedChatInvite#bdca2f75 flags:# revoked:flags.2?true peer:InputPeer link:string expire_date:flags.0?int usage_limit:flags.1?int request_needed:flags.3?Bool title:flags.4?string = messages.ExportedChatInvite;
|
messages.editExportedChatInvite#bdca2f75 flags:# revoked:flags.2?true peer:InputPeer link:string expire_date:flags.0?int usage_limit:flags.1?int request_needed:flags.3?Bool title:flags.4?string = messages.ExportedChatInvite;
|
||||||
|
messages.deleteRevokedExportedChatInvites#56987bd5 peer:InputPeer admin_id:InputUser = Bool;
|
||||||
messages.deleteExportedChatInvite#d464a42b peer:InputPeer link:string = Bool;
|
messages.deleteExportedChatInvite#d464a42b peer:InputPeer link:string = Bool;
|
||||||
|
messages.getChatInviteImporters#df04dd4e flags:# requested:flags.0?true peer:InputPeer link:flags.1?string q:flags.2?string offset_date:int offset_user:InputUser limit:int = messages.ChatInviteImporters;
|
||||||
messages.getMessageReadParticipants#2c6f97b7 peer:InputPeer msg_id:int = Vector<long>;
|
messages.getMessageReadParticipants#2c6f97b7 peer:InputPeer msg_id:int = Vector<long>;
|
||||||
|
messages.hideChatJoinRequest#7fe7e815 flags:# approved:flags.0?true peer:InputPeer user_id:InputUser = Updates;
|
||||||
|
messages.hideAllChatJoinRequests#e085f4ea flags:# approved:flags.0?true peer:InputPeer link:flags.1?string = Updates;
|
||||||
messages.toggleNoForwards#b11eafa2 peer:InputPeer enabled:Bool = Updates;
|
messages.toggleNoForwards#b11eafa2 peer:InputPeer enabled:Bool = Updates;
|
||||||
messages.saveDefaultSendAs#ccfddf96 peer:InputPeer send_as:InputPeer = Bool;
|
messages.saveDefaultSendAs#ccfddf96 peer:InputPeer send_as:InputPeer = Bool;
|
||||||
messages.sendReaction#25690ce4 flags:# peer:InputPeer msg_id:int reaction:flags.0?string = Updates;
|
messages.sendReaction#25690ce4 flags:# peer:InputPeer msg_id:int reaction:flags.0?string = Updates;
|
||||||
|
|||||||
@ -77,7 +77,9 @@
|
|||||||
"messages.createChat",
|
"messages.createChat",
|
||||||
"messages.getExportedChatInvites",
|
"messages.getExportedChatInvites",
|
||||||
"messages.editExportedChatInvite",
|
"messages.editExportedChatInvite",
|
||||||
|
"messages.deleteRevokedExportedChatInvites",
|
||||||
"messages.deleteExportedChatInvite",
|
"messages.deleteExportedChatInvite",
|
||||||
|
"messages.getChatInviteImporters",
|
||||||
"messages.getDhConfig",
|
"messages.getDhConfig",
|
||||||
"messages.readMessageContents",
|
"messages.readMessageContents",
|
||||||
"messages.getStickers",
|
"messages.getStickers",
|
||||||
@ -132,6 +134,8 @@
|
|||||||
"messages.unpinAllMessages",
|
"messages.unpinAllMessages",
|
||||||
"messages.deleteChat",
|
"messages.deleteChat",
|
||||||
"messages.getMessageReadParticipants",
|
"messages.getMessageReadParticipants",
|
||||||
|
"messages.hideChatJoinRequest",
|
||||||
|
"messages.hideAllChatJoinRequests",
|
||||||
"messages.toggleNoForwards",
|
"messages.toggleNoForwards",
|
||||||
"messages.saveDefaultSendAs",
|
"messages.saveDefaultSendAs",
|
||||||
"updates.getState",
|
"updates.getState",
|
||||||
|
|||||||
@ -30,7 +30,6 @@ import {
|
|||||||
updateChatListSecondaryInfo,
|
updateChatListSecondaryInfo,
|
||||||
updateManagementProgress,
|
updateManagementProgress,
|
||||||
leaveChat,
|
leaveChat,
|
||||||
updateManagement,
|
|
||||||
} from '../../reducers';
|
} from '../../reducers';
|
||||||
import {
|
import {
|
||||||
selectChat,
|
selectChat,
|
||||||
@ -611,82 +610,6 @@ addReducer('acceptInviteConfirmation', (global, actions, payload) => {
|
|||||||
})();
|
})();
|
||||||
});
|
});
|
||||||
|
|
||||||
addReducer('loadExportedChatInvites', (global, actions, payload) => {
|
|
||||||
const {
|
|
||||||
chatId, adminId, isRevoked, limit,
|
|
||||||
} = payload!;
|
|
||||||
const peer = selectChat(global, chatId);
|
|
||||||
const admin = selectUser(global, adminId || global.currentUserId);
|
|
||||||
if (!peer || !admin) return;
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
const result = await callApi('fetchExportedChatInvites', {
|
|
||||||
peer, admin, isRevoked, limit,
|
|
||||||
});
|
|
||||||
if (!result) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setGlobal(updateManagement(getGlobal(), chatId, { invites: result }));
|
|
||||||
})();
|
|
||||||
});
|
|
||||||
|
|
||||||
addReducer('editExportedChatInvite', (global, actions, payload) => {
|
|
||||||
const {
|
|
||||||
chatId, link, isRevoked, expireDate, usageLimit, isRequestNeeded, title,
|
|
||||||
} = payload!;
|
|
||||||
const peer = selectChat(global, chatId);
|
|
||||||
if (!peer) return;
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
const result = await callApi('editExportedChatInvite', {
|
|
||||||
peer,
|
|
||||||
link,
|
|
||||||
isRevoked,
|
|
||||||
expireDate,
|
|
||||||
usageLimit,
|
|
||||||
isRequestNeeded,
|
|
||||||
title,
|
|
||||||
});
|
|
||||||
if (!result) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
global = getGlobal();
|
|
||||||
let invites = global.management.byChatId[chatId].invites || [];
|
|
||||||
const { oldInvite, newInvite } = result;
|
|
||||||
invites = invites.filter((current) => current.link !== oldInvite.link);
|
|
||||||
setGlobal(updateManagement(global, chatId, {
|
|
||||||
invites: [...invites, newInvite],
|
|
||||||
}));
|
|
||||||
})();
|
|
||||||
});
|
|
||||||
|
|
||||||
addReducer('exportChatInvite', (global, actions, payload) => {
|
|
||||||
const {
|
|
||||||
chatId, expireDate, usageLimit, isRequestNeeded, title,
|
|
||||||
} = payload!;
|
|
||||||
const peer = selectChat(global, chatId);
|
|
||||||
if (!peer) return;
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
const result = await callApi('exportChatInvite', {
|
|
||||||
peer,
|
|
||||||
expireDate,
|
|
||||||
usageLimit,
|
|
||||||
isRequestNeeded,
|
|
||||||
title,
|
|
||||||
});
|
|
||||||
if (!result) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
global = getGlobal();
|
|
||||||
const invites = global.management.byChatId[chatId].invites || [];
|
|
||||||
setGlobal(updateManagement(global, chatId, {
|
|
||||||
invites: [...invites, result],
|
|
||||||
}));
|
|
||||||
})();
|
|
||||||
});
|
|
||||||
|
|
||||||
addReducer('openChatByUsername', (global, actions, payload) => {
|
addReducer('openChatByUsername', (global, actions, payload) => {
|
||||||
const {
|
const {
|
||||||
username, messageId, commentId, startParam,
|
username, messageId, commentId, startParam,
|
||||||
|
|||||||
@ -2,8 +2,8 @@ import { addReducer, getGlobal, setGlobal } from '../../../lib/teact/teactn';
|
|||||||
|
|
||||||
import { ManagementProgress } from '../../../types';
|
import { ManagementProgress } from '../../../types';
|
||||||
import { callApi } from '../../../api/gramjs';
|
import { callApi } from '../../../api/gramjs';
|
||||||
import { updateManagement, updateManagementProgress } from '../../reducers';
|
import { updateChat, updateManagement, updateManagementProgress } from '../../reducers';
|
||||||
import { selectChat, selectCurrentMessageList } from '../../selectors';
|
import { selectChat, selectCurrentMessageList, selectUser } from '../../selectors';
|
||||||
import { isChatBasicGroup } from '../../helpers';
|
import { isChatBasicGroup } from '../../helpers';
|
||||||
|
|
||||||
addReducer('checkPublicLink', (global, actions, payload) => {
|
addReducer('checkPublicLink', (global, actions, payload) => {
|
||||||
@ -82,3 +82,252 @@ addReducer('setEditingExportedInvite', (global, actions, payload) => {
|
|||||||
|
|
||||||
setGlobal(updateManagement(global, chatId, { editingInvite: invite }));
|
setGlobal(updateManagement(global, chatId, { editingInvite: invite }));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addReducer('setOpenedInviteInfo', (global, actions, payload) => {
|
||||||
|
const { chatId, invite } = payload;
|
||||||
|
|
||||||
|
const update = invite ? { inviteInfo: { invite } } : { inviteInfo: undefined };
|
||||||
|
|
||||||
|
setGlobal(updateManagement(global, chatId, update));
|
||||||
|
});
|
||||||
|
|
||||||
|
addReducer('loadExportedChatInvites', (global, actions, payload) => {
|
||||||
|
const {
|
||||||
|
chatId, adminId, isRevoked, limit,
|
||||||
|
} = payload!;
|
||||||
|
const peer = selectChat(global, chatId);
|
||||||
|
const admin = selectUser(global, adminId || global.currentUserId);
|
||||||
|
if (!peer || !admin) return;
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const result = await callApi('fetchExportedChatInvites', {
|
||||||
|
peer, admin, isRevoked, limit,
|
||||||
|
});
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const update = isRevoked ? { revokedInvites: result } : { invites: result };
|
||||||
|
|
||||||
|
setGlobal(updateManagement(getGlobal(), chatId, update));
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
|
||||||
|
addReducer('editExportedChatInvite', (global, actions, payload) => {
|
||||||
|
const {
|
||||||
|
chatId, link, isRevoked, expireDate, usageLimit, isRequestNeeded, title,
|
||||||
|
} = payload!;
|
||||||
|
const peer = selectChat(global, chatId);
|
||||||
|
if (!peer) return;
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const result = await callApi('editExportedChatInvite', {
|
||||||
|
peer,
|
||||||
|
link,
|
||||||
|
isRevoked,
|
||||||
|
expireDate,
|
||||||
|
usageLimit,
|
||||||
|
isRequestNeeded,
|
||||||
|
title,
|
||||||
|
});
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
global = getGlobal();
|
||||||
|
let invites = global.management.byChatId[chatId].invites || [];
|
||||||
|
const revokedInvites = global.management.byChatId[chatId].revokedInvites || [];
|
||||||
|
const { oldInvite, newInvite } = result;
|
||||||
|
invites = invites.filter((current) => current.link !== oldInvite.link);
|
||||||
|
if (newInvite.isRevoked) {
|
||||||
|
revokedInvites.unshift(newInvite);
|
||||||
|
} else {
|
||||||
|
invites.push(newInvite);
|
||||||
|
}
|
||||||
|
setGlobal(updateManagement(global, chatId, {
|
||||||
|
invites,
|
||||||
|
revokedInvites,
|
||||||
|
}));
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
|
||||||
|
addReducer('exportChatInvite', (global, actions, payload) => {
|
||||||
|
const {
|
||||||
|
chatId, expireDate, usageLimit, isRequestNeeded, title,
|
||||||
|
} = payload!;
|
||||||
|
const peer = selectChat(global, chatId);
|
||||||
|
if (!peer) return;
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const result = await callApi('exportChatInvite', {
|
||||||
|
peer,
|
||||||
|
expireDate,
|
||||||
|
usageLimit,
|
||||||
|
isRequestNeeded,
|
||||||
|
title,
|
||||||
|
});
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
global = getGlobal();
|
||||||
|
const invites = global.management.byChatId[chatId].invites || [];
|
||||||
|
setGlobal(updateManagement(global, chatId, {
|
||||||
|
invites: [...invites, result],
|
||||||
|
}));
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
|
||||||
|
addReducer('deleteExportedChatInvite', (global, actions, payload) => {
|
||||||
|
const {
|
||||||
|
chatId, link,
|
||||||
|
} = payload!;
|
||||||
|
const peer = selectChat(global, chatId);
|
||||||
|
if (!peer) return;
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const result = await callApi('deleteExportedChatInvite', {
|
||||||
|
peer,
|
||||||
|
link,
|
||||||
|
});
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
global = getGlobal();
|
||||||
|
const managementState = global.management.byChatId[chatId];
|
||||||
|
setGlobal(updateManagement(global, chatId, {
|
||||||
|
invites: managementState?.invites?.filter((invite) => invite.link !== link),
|
||||||
|
revokedInvites: managementState?.revokedInvites?.filter((invite) => invite.link !== link),
|
||||||
|
}));
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
|
||||||
|
addReducer('deleteRevokedExportedChatInvites', (global, actions, payload) => {
|
||||||
|
const {
|
||||||
|
chatId, adminId,
|
||||||
|
} = payload!;
|
||||||
|
const peer = selectChat(global, chatId);
|
||||||
|
const admin = selectUser(global, adminId || global.currentUserId);
|
||||||
|
if (!peer || !admin) return;
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const result = await callApi('deleteRevokedExportedChatInvites', {
|
||||||
|
peer,
|
||||||
|
admin,
|
||||||
|
});
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
global = getGlobal();
|
||||||
|
setGlobal(updateManagement(global, chatId, {
|
||||||
|
revokedInvites: [],
|
||||||
|
}));
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
|
||||||
|
addReducer('loadChatInviteImporters', (global, actions, payload) => {
|
||||||
|
const {
|
||||||
|
chatId, link, offsetDate, offsetUserId, limit,
|
||||||
|
} = payload!;
|
||||||
|
const peer = selectChat(global, chatId);
|
||||||
|
const offsetUser = selectUser(global, offsetUserId);
|
||||||
|
if (!peer || (offsetUserId && !offsetUser)) return;
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const result = await callApi('fetchChatInviteImporters', {
|
||||||
|
peer,
|
||||||
|
link,
|
||||||
|
offsetDate,
|
||||||
|
offsetUser,
|
||||||
|
limit,
|
||||||
|
});
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
global = getGlobal();
|
||||||
|
const currentInviteInfo = global.management.byChatId[chatId]?.inviteInfo;
|
||||||
|
if (!currentInviteInfo?.invite) return;
|
||||||
|
setGlobal(updateManagement(global, chatId, {
|
||||||
|
inviteInfo: {
|
||||||
|
...currentInviteInfo,
|
||||||
|
importers: result,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
|
||||||
|
addReducer('loadChatJoinRequests', (global, actions, payload) => {
|
||||||
|
const {
|
||||||
|
chatId, offsetDate, offsetUserId, limit,
|
||||||
|
} = payload!;
|
||||||
|
const peer = selectChat(global, chatId);
|
||||||
|
const offsetUser = selectUser(global, offsetUserId);
|
||||||
|
if (!peer || (offsetUserId && !offsetUser)) return;
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const result = await callApi('fetchChatInviteImporters', {
|
||||||
|
peer,
|
||||||
|
offsetDate,
|
||||||
|
offsetUser,
|
||||||
|
limit,
|
||||||
|
isRequested: true,
|
||||||
|
});
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
global = getGlobal();
|
||||||
|
setGlobal(updateChat(global, chatId, { joinRequests: result }));
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
|
||||||
|
addReducer('hideChatJoinRequest', (global, actions, payload) => {
|
||||||
|
const {
|
||||||
|
chatId, userId, isApproved,
|
||||||
|
} = payload!;
|
||||||
|
const peer = selectChat(global, chatId);
|
||||||
|
const user = selectUser(global, userId);
|
||||||
|
if (!peer || !user) return;
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const result = await callApi('hideChatJoinRequest', {
|
||||||
|
peer,
|
||||||
|
user,
|
||||||
|
isApproved,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result) return;
|
||||||
|
global = getGlobal();
|
||||||
|
const targetChat = selectChat(global, chatId);
|
||||||
|
if (!targetChat) return;
|
||||||
|
setGlobal(updateChat(global, chatId, {
|
||||||
|
joinRequests: targetChat.joinRequests?.filter((importer) => importer.userId !== userId),
|
||||||
|
}));
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
|
||||||
|
addReducer('hideAllChatJoinRequests', (global, actions, payload) => {
|
||||||
|
const {
|
||||||
|
chatId, isApproved, link,
|
||||||
|
} = payload!;
|
||||||
|
const peer = selectChat(global, chatId);
|
||||||
|
if (!peer) return;
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const result = await callApi('hideAllChatJoinRequests', {
|
||||||
|
peer,
|
||||||
|
isApproved,
|
||||||
|
link,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result) return;
|
||||||
|
global = getGlobal();
|
||||||
|
const targetChat = selectChat(global, chatId);
|
||||||
|
if (!targetChat) return;
|
||||||
|
|
||||||
|
setGlobal(updateChat(global, chatId, {
|
||||||
|
joinRequests: [],
|
||||||
|
fullInfo: {
|
||||||
|
...targetChat.fullInfo,
|
||||||
|
recentRequesterIds: [],
|
||||||
|
requestsPending: 0,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
|||||||
@ -382,5 +382,21 @@ addReducer('apiUpdate', (global, actions, update: ApiUpdate) => {
|
|||||||
actions.showDialog({ data });
|
actions.showDialog({ data });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'updatePendingJoinRequests': {
|
||||||
|
const { chatId, requestsPending, recentRequesterIds } = update;
|
||||||
|
const chat = global.chats.byId[chatId];
|
||||||
|
if (chat) {
|
||||||
|
global = updateChat(global, chatId, {
|
||||||
|
fullInfo: {
|
||||||
|
...chat.fullInfo,
|
||||||
|
requestsPending,
|
||||||
|
recentRequesterIds,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
setGlobal(global);
|
||||||
|
actions.loadChatJoinRequests({ chatId });
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -54,6 +54,29 @@ addReducer('toggleManagement', (global): GlobalState | undefined => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addReducer('requestNextManagementScreen', (global, actions, payload): GlobalState | undefined => {
|
||||||
|
const { screen } = payload || {};
|
||||||
|
const { chatId } = selectCurrentMessageList(global) || {};
|
||||||
|
|
||||||
|
if (!chatId) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...global,
|
||||||
|
management: {
|
||||||
|
byChatId: {
|
||||||
|
...global.management.byChatId,
|
||||||
|
[chatId]: {
|
||||||
|
...global.management.byChatId[chatId],
|
||||||
|
isActive: true,
|
||||||
|
nextScreen: screen,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
addReducer('closeManagement', (global): GlobalState | undefined => {
|
addReducer('closeManagement', (global): GlobalState | undefined => {
|
||||||
const { chatId } = selectCurrentMessageList(global) || {};
|
const { chatId } = selectCurrentMessageList(global) || {};
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
ApiBotInlineMediaResult, ApiBotInlineResult, ApiBotInlineSwitchPm,
|
ApiBotInlineMediaResult, ApiBotInlineResult, ApiBotInlineSwitchPm,
|
||||||
|
ApiChatInviteImporter,
|
||||||
ApiExportedInvite,
|
ApiExportedInvite,
|
||||||
ApiLanguage, ApiMessage, ApiShippingAddress, ApiStickerSet,
|
ApiLanguage, ApiMessage, ApiShippingAddress, ApiStickerSet,
|
||||||
} from '../api/types';
|
} from '../api/types';
|
||||||
@ -283,10 +284,16 @@ export enum ManagementProgress {
|
|||||||
|
|
||||||
export interface ManagementState {
|
export interface ManagementState {
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
|
nextScreen?: ManagementScreens;
|
||||||
isUsernameAvailable?: boolean;
|
isUsernameAvailable?: boolean;
|
||||||
error?: string;
|
error?: string;
|
||||||
invites?: ApiExportedInvite[];
|
invites?: ApiExportedInvite[];
|
||||||
|
revokedInvites?: ApiExportedInvite[];
|
||||||
editingInvite?: ApiExportedInvite;
|
editingInvite?: ApiExportedInvite;
|
||||||
|
inviteInfo?: {
|
||||||
|
invite: ApiExportedInvite;
|
||||||
|
importers?: ApiChatInviteImporter[];
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum NewChatMembersProgress {
|
export enum NewChatMembersProgress {
|
||||||
@ -334,6 +341,8 @@ export enum ManagementScreens {
|
|||||||
Invites,
|
Invites,
|
||||||
EditInvite,
|
EditInvite,
|
||||||
Reactions,
|
Reactions,
|
||||||
|
InviteInfo,
|
||||||
|
JoinRequests,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ManagementType = 'user' | 'group' | 'channel';
|
export type ManagementType = 'user' | 'group' | 'channel';
|
||||||
|
|||||||
@ -12,6 +12,10 @@ const MAX_DAY_IN_MONTH = 31;
|
|||||||
const MAX_MONTH_IN_YEAR = 12;
|
const MAX_MONTH_IN_YEAR = 12;
|
||||||
export const MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000;
|
export const MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
|
export function isToday(date: Date) {
|
||||||
|
return getDayStart(new Date()) === getDayStart(date);
|
||||||
|
}
|
||||||
|
|
||||||
export function getDayStart(datetime: number | Date) {
|
export function getDayStart(datetime: number | Date) {
|
||||||
const date = new Date(datetime);
|
const date = new Date(datetime);
|
||||||
date.setHours(0, 0, 0, 0);
|
date.setHours(0, 0, 0, 0);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user