Management / Join Requests: Add animated stickers, various fixes (#1679)
This commit is contained in:
parent
71ef93e993
commit
bb0d846838
1
src/@types/global.d.ts
vendored
1
src/@types/global.d.ts
vendored
@ -46,6 +46,7 @@ type EmojiWithSkins = Record<number, Emoji>;
|
||||
type AllEmojis = Record<string, Emoji | EmojiWithSkins>;
|
||||
|
||||
declare module '*.png';
|
||||
declare module '*.tgs';
|
||||
|
||||
declare module 'pako/dist/pako_inflate' {
|
||||
function inflate(...args: any[]): string;
|
||||
|
||||
BIN
src/assets/tgs/invites/Invite.tgs
Normal file
BIN
src/assets/tgs/invites/Invite.tgs
Normal file
Binary file not shown.
BIN
src/assets/tgs/invites/Requests.tgs
Normal file
BIN
src/assets/tgs/invites/Requests.tgs
Normal file
Binary file not shown.
@ -2,43 +2,31 @@ import { ApiMediaFormat } from '../../../api/types';
|
||||
|
||||
import * as mediaLoader from '../../../util/mediaLoader';
|
||||
|
||||
// @ts-ignore
|
||||
import MonkeyIdle from '../../../assets/tgs/monkeys/TwoFactorSetupMonkeyIdle.tgs';
|
||||
// @ts-ignore
|
||||
import MonkeyTracking from '../../../assets/tgs/monkeys/TwoFactorSetupMonkeyTracking.tgs';
|
||||
// @ts-ignore
|
||||
import MonkeyClose from '../../../assets/tgs/monkeys/TwoFactorSetupMonkeyClose.tgs';
|
||||
// @ts-ignore
|
||||
import MonkeyPeek from '../../../assets/tgs/monkeys/TwoFactorSetupMonkeyPeek.tgs';
|
||||
// @ts-ignore
|
||||
|
||||
import FoldersAll from '../../../assets/tgs/settings/FoldersAll.tgs';
|
||||
// @ts-ignore
|
||||
import FoldersNew from '../../../assets/tgs/settings/FoldersNew.tgs';
|
||||
// @ts-ignore
|
||||
import DiscussionGroups from '../../../assets/tgs/settings/DiscussionGroupsDucks.tgs';
|
||||
// @ts-ignore
|
||||
|
||||
import CameraFlip from '../../../assets/tgs/calls/CameraFlip.tgs';
|
||||
// @ts-ignore
|
||||
import HandFilled from '../../../assets/tgs/calls/HandFilled.tgs';
|
||||
// @ts-ignore
|
||||
import HandOutline from '../../../assets/tgs/calls/HandOutline.tgs';
|
||||
// @ts-ignore
|
||||
import Speaker from '../../../assets/tgs/calls/Speaker.tgs';
|
||||
// @ts-ignore
|
||||
import VoiceAllowTalk from '../../../assets/tgs/calls/VoiceAllowTalk.tgs';
|
||||
// @ts-ignore
|
||||
import VoiceMini from '../../../assets/tgs/calls/VoiceMini.tgs';
|
||||
// @ts-ignore
|
||||
import VoiceMuted from '../../../assets/tgs/calls/VoiceMuted.tgs';
|
||||
// @ts-ignore
|
||||
import VoiceOutlined from '../../../assets/tgs/calls/VoiceOutlined.tgs';
|
||||
// @ts-ignore
|
||||
|
||||
import Peach from '../../../assets/tgs/animatedEmojis/Peach.tgs';
|
||||
// @ts-ignore
|
||||
import Eggplant from '../../../assets/tgs/animatedEmojis/Eggplant.tgs';
|
||||
// @ts-ignore
|
||||
import Cumshot from '../../../assets/tgs/animatedEmojis/Cumshot.tgs';
|
||||
|
||||
import JoinRequest from '../../../assets/tgs/invites/Requests.tgs';
|
||||
import Invite from '../../../assets/tgs/invites/Invite.tgs';
|
||||
|
||||
export const ANIMATED_STICKERS_PATHS = {
|
||||
MonkeyIdle,
|
||||
MonkeyTracking,
|
||||
@ -58,6 +46,8 @@ export const ANIMATED_STICKERS_PATHS = {
|
||||
Peach,
|
||||
Eggplant,
|
||||
Cumshot,
|
||||
JoinRequest,
|
||||
Invite,
|
||||
};
|
||||
|
||||
export default function getAnimationData(name: keyof typeof ANIMATED_STICKERS_PATHS) {
|
||||
|
||||
@ -22,7 +22,12 @@ type StateProps = {
|
||||
};
|
||||
|
||||
const Dialogs: FC<StateProps> = ({ dialogs }) => {
|
||||
const { dismissDialog, acceptInviteConfirmation, sendMessage } = getDispatch();
|
||||
const {
|
||||
dismissDialog,
|
||||
acceptInviteConfirmation,
|
||||
sendMessage,
|
||||
showNotification,
|
||||
} = getDispatch();
|
||||
const [isModalOpen, openModal, closeModal] = useFlag();
|
||||
|
||||
const lang = useLang();
|
||||
@ -60,6 +65,9 @@ const Dialogs: FC<StateProps> = ({ dialogs }) => {
|
||||
acceptInviteConfirmation({
|
||||
hash,
|
||||
});
|
||||
showNotification({
|
||||
message: isChannel ? lang('RequestToJoinChannelSentDescription') : lang('RequestToJoinGroupSentDescription'),
|
||||
});
|
||||
closeModal();
|
||||
};
|
||||
|
||||
@ -79,8 +87,8 @@ const Dialogs: FC<StateProps> = ({ dialogs }) => {
|
||||
header={renderInviteHeader(title, photo)}
|
||||
onCloseAnimationEnd={dismissDialog}
|
||||
>
|
||||
{about && <p className="modal-about">{renderText(about)}</p>}
|
||||
{participantsCount !== undefined && <p>{participantsText}</p>}
|
||||
{participantsCount !== undefined && <p className="modal-help">{participantsText}</p>}
|
||||
{about && <p className="modal-about">{renderText(about, ['br'])}</p>}
|
||||
{isRequestNeeded && (
|
||||
<p className="modal-help">
|
||||
{isChannel
|
||||
|
||||
@ -22,12 +22,15 @@ import {
|
||||
} from '../../modules/helpers';
|
||||
import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useFlag from '../../hooks/useFlag';
|
||||
import { getDayStartAt } from '../../util/dateFormat';
|
||||
|
||||
import SearchInput from '../ui/SearchInput';
|
||||
import Button from '../ui/Button';
|
||||
import Transition from '../ui/Transition';
|
||||
import ConfirmDialog from '../ui/ConfirmDialog';
|
||||
|
||||
import './RightHeader.scss';
|
||||
import { getDayStartAt } from '../../util/dateFormat';
|
||||
|
||||
type OwnProps = {
|
||||
chatId?: string;
|
||||
@ -131,6 +134,7 @@ const RightHeader: FC<OwnProps & StateProps> = ({
|
||||
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const backButtonRef = useRef<HTMLDivElement>(null);
|
||||
const [isDeleteDialogOpen, openDeleteDialog, closeDeleteDialog] = useFlag();
|
||||
|
||||
const handleEditInviteClick = useCallback(() => {
|
||||
setEditingExportedInvite({ chatId: chatId!, invite: currentInviteInfo! });
|
||||
@ -140,7 +144,8 @@ const RightHeader: FC<OwnProps & StateProps> = ({
|
||||
const handleDeleteInviteClick = useCallback(() => {
|
||||
deleteExportedChatInvite({ chatId: chatId!, link: currentInviteInfo!.link });
|
||||
onScreenSelect(ManagementScreens.Invites);
|
||||
}, [chatId, currentInviteInfo, deleteExportedChatInvite, onScreenSelect]);
|
||||
closeDeleteDialog();
|
||||
}, [chatId, closeDeleteDialog, currentInviteInfo, deleteExportedChatInvite, onScreenSelect]);
|
||||
|
||||
const handleMessageSearchQueryChange = useCallback((query: string) => {
|
||||
setLocalTextSearchQuery({ query });
|
||||
@ -305,15 +310,26 @@ const RightHeader: FC<OwnProps & StateProps> = ({
|
||||
</Button>
|
||||
)}
|
||||
{currentInviteInfo && currentInviteInfo.isRevoked && (
|
||||
<Button
|
||||
round
|
||||
color="danger"
|
||||
size="smaller"
|
||||
ariaLabel={lang('Delete')}
|
||||
onClick={handleDeleteInviteClick}
|
||||
>
|
||||
<i className="icon-delete" />
|
||||
</Button>
|
||||
<>
|
||||
<Button
|
||||
round
|
||||
color="danger"
|
||||
size="smaller"
|
||||
ariaLabel={lang('Delete')}
|
||||
onClick={openDeleteDialog}
|
||||
>
|
||||
<i className="icon-delete" />
|
||||
</Button>
|
||||
<ConfirmDialog
|
||||
isOpen={isDeleteDialogOpen}
|
||||
onClose={closeDeleteDialog}
|
||||
title={lang('DeleteLink')}
|
||||
text={lang('DeleteLinkHelp')}
|
||||
confirmIsDestructive
|
||||
confirmLabel={lang('Delete')}
|
||||
confirmHandler={handleDeleteInviteClick}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</section>
|
||||
</>
|
||||
|
||||
@ -21,17 +21,29 @@
|
||||
&__user {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__user-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
&__user-subtitle {
|
||||
color: var(--color-text-secondary);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__user-name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__date {
|
||||
|
||||
@ -50,7 +50,7 @@ const JoinRequest: FC<OwnProps & StateProps> = ({
|
||||
? formatTime(lang, fixedDate) : formatHumanDate(lang, fixedDate, true, false, true);
|
||||
|
||||
const handleUserClick = () => {
|
||||
openUserInfo({ userId });
|
||||
openUserInfo({ id: userId });
|
||||
};
|
||||
|
||||
const handleAcceptRequest = useCallback(() => {
|
||||
|
||||
@ -111,8 +111,7 @@ const ManageGroupMembers: FC<OwnProps & StateProps> = ({
|
||||
return true;
|
||||
}
|
||||
|
||||
return !user.isSelf
|
||||
&& (isChannel || user.canBeInvitedToGroup || !isUserBot(user))
|
||||
return (isChannel || user.canBeInvitedToGroup || !isUserBot(user))
|
||||
&& (!noAdmins || !adminIds.includes(contactId));
|
||||
}),
|
||||
chatsById,
|
||||
|
||||
@ -59,6 +59,7 @@ const ManageInvite: FC<OwnProps & StateProps> = ({
|
||||
const [selectedExpireOption, setSelectedExpireOption] = useState('unlimited');
|
||||
const [customUsageLimit, setCustomUsageLimit] = useState<number | undefined>(10);
|
||||
const [selectedUsageOption, setSelectedUsageOption] = useState('0');
|
||||
const [isSubmitBlocked, setIsSubmitBlocked] = useState(false);
|
||||
|
||||
useHistoryBack(isActive, onClose);
|
||||
|
||||
@ -81,8 +82,9 @@ const ManageInvite: FC<OwnProps & StateProps> = ({
|
||||
setCustomUsageLimit(usageLimit);
|
||||
}
|
||||
if (expireDate) {
|
||||
const minSafeDate = getServerTime(serverTimeOffset) + DEFAULT_CUSTOM_EXPIRE_DATE;
|
||||
setSelectedExpireOption('custom');
|
||||
setCustomExpireDate(expireDate * 1000);
|
||||
setCustomExpireDate(Math.max(expireDate, minSafeDate) * 1000);
|
||||
}
|
||||
if (editingIsRequestNeeded) {
|
||||
setIsRequestNeeded(true);
|
||||
@ -108,6 +110,7 @@ const ManageInvite: FC<OwnProps & StateProps> = ({
|
||||
}, [closeCalendar]);
|
||||
|
||||
const handleSaveClick = useCallback(() => {
|
||||
setIsSubmitBlocked(true);
|
||||
const usageLimit = selectedUsageOption === 'custom' ? customUsageLimit : selectedUsageOption;
|
||||
let expireDate;
|
||||
switch (selectedExpireOption) {
|
||||
@ -240,6 +243,7 @@ const ManageInvite: FC<OwnProps & StateProps> = ({
|
||||
<FloatingActionButton
|
||||
isShown
|
||||
onClick={handleSaveClick}
|
||||
disabled={isSubmitBlocked}
|
||||
ariaLabel={editingInvite ? lang('SaveLink') : lang('CreateLink')}
|
||||
>
|
||||
<i className="icon-check" />
|
||||
|
||||
@ -10,10 +10,13 @@ import useLang from '../../../hooks/useLang';
|
||||
import { copyTextToClipboard } from '../../../util/clipboard';
|
||||
import { getServerTime } from '../../../util/serverTime';
|
||||
import { formatFullDate, formatMediaDateTime, formatTime } from '../../../util/dateFormat';
|
||||
import { isChatChannel } from '../../../modules/helpers';
|
||||
import { selectChat } from '../../../modules/selectors';
|
||||
|
||||
import PrivateChatInfo from '../../common/PrivateChatInfo';
|
||||
import ListItem from '../../ui/ListItem';
|
||||
import Button from '../../ui/Button';
|
||||
import Spinner from '../../ui/Spinner';
|
||||
|
||||
type OwnProps = {
|
||||
chatId: string;
|
||||
@ -24,7 +27,9 @@ type OwnProps = {
|
||||
type StateProps = {
|
||||
invite?: ApiExportedInvite;
|
||||
importers?: ApiChatInviteImporter[];
|
||||
requesters?: ApiChatInviteImporter[];
|
||||
admin?: ApiUser;
|
||||
isChannel?: boolean;
|
||||
serverTimeOffset: number;
|
||||
};
|
||||
|
||||
@ -32,6 +37,8 @@ const ManageInviteInfo: FC<OwnProps & StateProps> = ({
|
||||
chatId,
|
||||
invite,
|
||||
importers,
|
||||
requesters,
|
||||
isChannel,
|
||||
isActive,
|
||||
serverTimeOffset,
|
||||
onClose,
|
||||
@ -39,6 +46,7 @@ const ManageInviteInfo: FC<OwnProps & StateProps> = ({
|
||||
const {
|
||||
showNotification,
|
||||
loadChatInviteImporters,
|
||||
loadChatInviteRequesters,
|
||||
openUserInfo,
|
||||
} = getDispatch();
|
||||
|
||||
@ -50,8 +58,11 @@ const ManageInviteInfo: FC<OwnProps & StateProps> = ({
|
||||
const isExpired = ((invite?.expireDate || 0) - getServerTime(serverTimeOffset)) < 0;
|
||||
|
||||
useEffect(() => {
|
||||
if (link) loadChatInviteImporters({ chatId, link });
|
||||
}, [chatId, link, loadChatInviteImporters]);
|
||||
if (link) {
|
||||
loadChatInviteImporters({ chatId, link });
|
||||
loadChatInviteRequesters({ chatId, link });
|
||||
}
|
||||
}, [chatId, link, loadChatInviteImporters, loadChatInviteRequesters]);
|
||||
|
||||
const handleCopyClicked = useCallback(() => {
|
||||
copyTextToClipboard(invite!.link);
|
||||
@ -63,8 +74,8 @@ const ManageInviteInfo: FC<OwnProps & StateProps> = ({
|
||||
useHistoryBack(isActive, onClose);
|
||||
|
||||
const renderImporters = () => {
|
||||
if (invite?.isRevoked) return undefined;
|
||||
if (!importers) return <p className="text-muted">{lang('Loading')}</p>;
|
||||
if (!importers?.length && requesters?.length) return undefined;
|
||||
if (!importers) return <Spinner />;
|
||||
return (
|
||||
<div className="section">
|
||||
<p>{importers.length ? lang('PeopleJoined', usage) : lang('NoOneJoined')}</p>
|
||||
@ -89,6 +100,31 @@ const ManageInviteInfo: FC<OwnProps & StateProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
const renderRequesters = () => {
|
||||
if (invite?.isRevoked) return undefined;
|
||||
if (!requesters && importers) return <Spinner />;
|
||||
if (!requesters?.length) return undefined;
|
||||
return (
|
||||
<div className="section">
|
||||
<p>{isChannel ? lang('SubscribeRequests') : lang('MemberRequests')}</p>
|
||||
<p className="text-muted">
|
||||
{requesters.map((requester) => (
|
||||
<ListItem
|
||||
className="chat-item-clickable scroll-item small-icon"
|
||||
onClick={() => openUserInfo({ id: requester.userId })}
|
||||
>
|
||||
<PrivateChatInfo
|
||||
userId={requester.userId}
|
||||
status={formatMediaDateTime(lang, requester.date * 1000, true)}
|
||||
forceShowSelf
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="Management ManageInviteInfo">
|
||||
<div className="custom-scroll">
|
||||
@ -98,7 +134,7 @@ const ManageInviteInfo: FC<OwnProps & StateProps> = ({
|
||||
{invite && (
|
||||
<>
|
||||
<div className="section">
|
||||
<h3>{invite.title || invite.link}</h3>
|
||||
<h3 className="link-title">{invite.title || invite.link}</h3>
|
||||
<input
|
||||
className="form-control"
|
||||
value={invite.link}
|
||||
@ -130,6 +166,7 @@ const ManageInviteInfo: FC<OwnProps & StateProps> = ({
|
||||
</div>
|
||||
)}
|
||||
{renderImporters()}
|
||||
{renderRequesters()}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
@ -140,12 +177,15 @@ const ManageInviteInfo: FC<OwnProps & StateProps> = ({
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global, { chatId }): StateProps => {
|
||||
const { inviteInfo } = global.management.byChatId[chatId];
|
||||
const invite = inviteInfo?.invite;
|
||||
const importers = inviteInfo?.importers;
|
||||
const { invite, importers, requesters } = inviteInfo || {};
|
||||
const chat = selectChat(global, chatId);
|
||||
const isChannel = chat && isChatChannel(chat);
|
||||
|
||||
return {
|
||||
invite,
|
||||
importers,
|
||||
requesters,
|
||||
isChannel,
|
||||
serverTimeOffset: global.serverTimeOffset,
|
||||
};
|
||||
},
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
import React, {
|
||||
FC, memo, useCallback, useMemo, useState,
|
||||
FC, memo, useCallback, useEffect, useMemo, useState,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { getDispatch, withGlobal } from '../../../lib/teact/teactn';
|
||||
|
||||
import { ApiChat, ApiExportedInvite } from '../../../api/types';
|
||||
import { ManagementScreens } from '../../../types';
|
||||
|
||||
import { STICKER_SIZE_INVITES } from '../../../config';
|
||||
import getAnimationData from '../../common/helpers/animatedAssets';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import { formatCountdown, MILLISECONDS_IN_DAY } from '../../../util/dateFormat';
|
||||
@ -16,6 +18,7 @@ import { copyTextToClipboard } from '../../../util/clipboard';
|
||||
import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment';
|
||||
import { getServerTime } from '../../../util/serverTime';
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import { isChatChannel } from '../../../modules/helpers';
|
||||
|
||||
import ListItem from '../../ui/ListItem';
|
||||
import NothingFound from '../../common/NothingFound';
|
||||
@ -23,6 +26,7 @@ import Button from '../../ui/Button';
|
||||
import DropdownMenu from '../../ui/DropdownMenu';
|
||||
import MenuItem from '../../ui/MenuItem';
|
||||
import ConfirmDialog from '../../ui/ConfirmDialog';
|
||||
import AnimatedSticker from '../../common/AnimatedSticker';
|
||||
|
||||
type OwnProps = {
|
||||
chatId: string;
|
||||
@ -33,6 +37,7 @@ type OwnProps = {
|
||||
|
||||
type StateProps = {
|
||||
chat?: ApiChat;
|
||||
isChannel?: boolean;
|
||||
exportedInvites?: ApiExportedInvite[];
|
||||
revokedExportedInvites?: ApiExportedInvite[];
|
||||
serverTimeOffset: number;
|
||||
@ -54,6 +59,7 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
|
||||
exportedInvites,
|
||||
revokedExportedInvites,
|
||||
isActive,
|
||||
isChannel,
|
||||
serverTimeOffset,
|
||||
onClose,
|
||||
onScreenSelect,
|
||||
@ -66,14 +72,26 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
|
||||
deleteRevokedExportedChatInvites,
|
||||
setOpenedInviteInfo,
|
||||
} = getDispatch();
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
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>();
|
||||
|
||||
const [animationData, setAnimationData] = useState<string>();
|
||||
const [isAnimationLoaded, setIsAnimationLoaded] = useState(false);
|
||||
const handleAnimationLoad = useCallback(() => setIsAnimationLoaded(true), []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!animationData) {
|
||||
getAnimationData('Invite').then(setAnimationData);
|
||||
}
|
||||
}, [animationData]);
|
||||
|
||||
useHistoryBack(isActive, onClose);
|
||||
const lang = useLang();
|
||||
|
||||
const hasDetailedCountdown = useMemo(() => {
|
||||
if (!exportedInvites) return undefined;
|
||||
@ -265,6 +283,20 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
|
||||
return (
|
||||
<div className="Management ManageInvites">
|
||||
<div className="custom-scroll">
|
||||
<div className="section">
|
||||
<div className="section-icon">
|
||||
{animationData && (
|
||||
<AnimatedSticker
|
||||
id="inviteDuck"
|
||||
size={STICKER_SIZE_INVITES}
|
||||
animationData={animationData}
|
||||
play={isAnimationLoaded}
|
||||
onLoad={handleAnimationLoad}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<p className='text-muted'>{isChannel ? lang('PrimaryLinkHelpChannel') : lang('PrimaryLinkHelp')}</p>
|
||||
</div>
|
||||
{primaryInviteLink && (
|
||||
<div className="section">
|
||||
<p className="text-muted">
|
||||
@ -347,6 +379,8 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
|
||||
onClose={closeDeleteRevokeAllDialog}
|
||||
title={lang('DeleteAllRevokedLinks')}
|
||||
text={lang('DeleteAllRevokedLinkHelp')}
|
||||
confirmIsDestructive
|
||||
confirmLabel={lang('DeleteAll')}
|
||||
confirmHandler={handleDeleteAllRevoked}
|
||||
/>
|
||||
<ConfirmDialog
|
||||
@ -354,6 +388,8 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
|
||||
onClose={closeRevokeDialog}
|
||||
title={lang('RevokeLink')}
|
||||
text={lang('RevokeAlert')}
|
||||
confirmIsDestructive
|
||||
confirmLabel={lang('RevokeButton')}
|
||||
confirmHandler={handleRevoke}
|
||||
/>
|
||||
<ConfirmDialog
|
||||
@ -361,6 +397,8 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
|
||||
onClose={closeDeleteDialog}
|
||||
title={lang('DeleteLink')}
|
||||
text={lang('DeleteLinkHelp')}
|
||||
confirmIsDestructive
|
||||
confirmLabel={lang('Delete')}
|
||||
confirmHandler={handleDelete}
|
||||
/>
|
||||
</div>
|
||||
@ -371,12 +409,14 @@ export default memo(withGlobal<OwnProps>(
|
||||
(global, { chatId }): StateProps => {
|
||||
const { invites, revokedInvites } = global.management.byChatId[chatId];
|
||||
const chat = selectChat(global, chatId);
|
||||
const isChannel = chat && isChatChannel(chat);
|
||||
|
||||
return {
|
||||
exportedInvites: invites,
|
||||
revokedExportedInvites: revokedInvites,
|
||||
chat,
|
||||
serverTimeOffset: global.serverTimeOffset,
|
||||
isChannel,
|
||||
};
|
||||
},
|
||||
)(ManageInvites));
|
||||
|
||||
@ -1,19 +1,23 @@
|
||||
import React, {
|
||||
FC, memo, useCallback, useEffect,
|
||||
FC, memo, useCallback, useEffect, useState,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { getDispatch, withGlobal } from '../../../lib/teact/teactn';
|
||||
|
||||
import { ApiChat } from '../../../api/types';
|
||||
|
||||
import { STICKER_SIZE_JOIN_REQUESTS } from '../../../config';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
import { selectChat } from '../../../modules/selectors';
|
||||
import { isChatChannel } from '../../../modules/helpers';
|
||||
import { isChatChannel, isUserId } from '../../../modules/helpers';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import getAnimationData from '../../common/helpers/animatedAssets';
|
||||
|
||||
import JoinRequest from './JoinRequest';
|
||||
import Button from '../../ui/Button';
|
||||
import ConfirmDialog from '../../ui/ConfirmDialog';
|
||||
import AnimatedSticker from '../../common/AnimatedSticker';
|
||||
import Spinner from '../../ui/Spinner';
|
||||
|
||||
type OwnProps = {
|
||||
chatId: string;
|
||||
@ -40,10 +44,20 @@ const ManageJoinRequests: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
const [animationData, setAnimationData] = useState<string>();
|
||||
const [isAnimationLoaded, setIsAnimationLoaded] = useState(false);
|
||||
const handleAnimationLoad = useCallback(() => setIsAnimationLoaded(true), []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!animationData) {
|
||||
getAnimationData('JoinRequest').then(setAnimationData);
|
||||
}
|
||||
}, [animationData]);
|
||||
|
||||
useHistoryBack(isActive, onClose);
|
||||
|
||||
useEffect(() => {
|
||||
if (!chat?.joinRequests) {
|
||||
if (!chat?.joinRequests && !isUserId(chatId)) {
|
||||
loadChatJoinRequests({ chatId });
|
||||
}
|
||||
}, [chat, chatId, loadChatJoinRequests]);
|
||||
@ -60,17 +74,34 @@ const ManageJoinRequests: FC<OwnProps & StateProps> = ({
|
||||
|
||||
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 className="section">
|
||||
<div className="section-icon">
|
||||
{animationData && (
|
||||
<AnimatedSticker
|
||||
id="joinRequestDucks"
|
||||
size={STICKER_SIZE_JOIN_REQUESTS}
|
||||
animationData={animationData}
|
||||
play={isAnimationLoaded}
|
||||
onLoad={handleAnimationLoad}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{Boolean(chat?.joinRequests?.length) && (
|
||||
<div className="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>
|
||||
<div className="section">
|
||||
<div className="custom-scroll" teactFastList>
|
||||
<p key="title">
|
||||
{chat?.joinRequests?.length ? lang('JoinRequests', chat?.joinRequests?.length) : lang('NoMemberRequests')}
|
||||
{!chat?.joinRequests ? lang('Loading') : chat.joinRequests.length
|
||||
? lang('JoinRequests', chat.joinRequests.length) : lang('NoMemberRequests')}
|
||||
</p>
|
||||
{!chat?.joinRequests && (
|
||||
<Spinner key="loading" />
|
||||
)}
|
||||
{chat?.joinRequests?.length === 0 && (
|
||||
<p className="text-muted" key="empty">
|
||||
{isChannel ? lang('NoSubscribeRequestsDescription') : lang('NoMemberRequestsDescription')}
|
||||
|
||||
@ -183,6 +183,10 @@
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.Spinner {
|
||||
margin: 2rem auto;
|
||||
}
|
||||
}
|
||||
|
||||
.ManageGroupMembers {
|
||||
@ -197,6 +201,7 @@
|
||||
.primary-link-input {
|
||||
cursor: pointer;
|
||||
margin-bottom: 1rem;
|
||||
padding-right: 3rem;
|
||||
}
|
||||
|
||||
.primary-link-more-menu {
|
||||
@ -230,6 +235,10 @@
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
}
|
||||
|
||||
.custom-scroll {
|
||||
padding-bottom: 4rem;
|
||||
}
|
||||
}
|
||||
|
||||
.ManageInviteInfo {
|
||||
@ -237,6 +246,10 @@
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.link-title {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.ManageJoinRequests {
|
||||
@ -251,7 +264,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
.ManageInvite, .ManageInvites {
|
||||
.ManageInvite,
|
||||
.ManageInvites {
|
||||
.hint {
|
||||
font-size: 0.875rem;
|
||||
margin-bottom: 0;
|
||||
|
||||
@ -114,7 +114,6 @@ const ListItem: FC<OwnProps> = ({
|
||||
|
||||
const handleSecondaryIconClick = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
if (disabled || e.button !== 0 || (!onSecondaryIconClick && !contextActions)) return;
|
||||
|
||||
e.stopPropagation();
|
||||
if (onSecondaryIconClick) {
|
||||
onSecondaryIconClick(e);
|
||||
@ -188,7 +187,8 @@ const ListItem: FC<OwnProps> = ({
|
||||
round
|
||||
color="translucent"
|
||||
size="smaller"
|
||||
onMouseDown={handleSecondaryIconClick}
|
||||
onClick={IS_TOUCH_ENV ? handleSecondaryIconClick : undefined}
|
||||
onMouseDown={!IS_TOUCH_ENV ? handleSecondaryIconClick : undefined}
|
||||
>
|
||||
<i className={`icon-${secondaryIcon}`} />
|
||||
</Button>
|
||||
|
||||
@ -140,7 +140,11 @@
|
||||
}
|
||||
|
||||
.modal-about {
|
||||
font-weight: bold;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 5;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.modal-help {
|
||||
|
||||
@ -121,8 +121,10 @@ export const STICKER_SIZE_SEARCH = 64;
|
||||
export const STICKER_SIZE_MODAL = 64;
|
||||
export const STICKER_SIZE_TWO_FA = 160;
|
||||
export const STICKER_SIZE_DISCUSSION_GROUPS = 140;
|
||||
export const STICKER_SIZE_FOLDER_SETTINGS = 80;
|
||||
export const STICKER_SIZE_FOLDER_SETTINGS = 100;
|
||||
export const STICKER_SIZE_INLINE_BOT_RESULT = 100;
|
||||
export const STICKER_SIZE_JOIN_REQUESTS = 140;
|
||||
export const STICKER_SIZE_INVITES = 140;
|
||||
export const RECENT_STICKERS_LIMIT = 20;
|
||||
export const MEMOJI_STICKER_ID = 'MEMOJI_STICKER';
|
||||
|
||||
|
||||
@ -558,6 +558,7 @@ export type ActionTypes = (
|
||||
'setEditingExportedInvite' | 'loadExportedChatInvites' | 'editExportedChatInvite' | 'exportChatInvite' |
|
||||
'deleteExportedChatInvite' | 'deleteRevokedExportedChatInvites' | 'setOpenedInviteInfo' | 'loadChatInviteImporters' |
|
||||
'loadChatJoinRequests' | 'hideChatJoinRequest' | 'hideAllChatJoinRequests' | 'requestNextManagementScreen' |
|
||||
'loadChatInviteRequesters' |
|
||||
// groups
|
||||
'togglePreHistoryHidden' | 'updateChatDefaultBannedRights' | 'updateChatMemberBannedRights' | 'updateChatAdmin' |
|
||||
'acceptInviteConfirmation' |
|
||||
|
||||
@ -243,7 +243,7 @@ addReducer('loadChatInviteImporters', (global, actions, payload) => {
|
||||
}
|
||||
global = getGlobal();
|
||||
const currentInviteInfo = global.management.byChatId[chatId]?.inviteInfo;
|
||||
if (!currentInviteInfo?.invite) return;
|
||||
if (!currentInviteInfo?.invite || currentInviteInfo.invite.link !== link) return;
|
||||
setGlobal(updateManagement(global, chatId, {
|
||||
inviteInfo: {
|
||||
...currentInviteInfo,
|
||||
@ -253,6 +253,38 @@ addReducer('loadChatInviteImporters', (global, actions, payload) => {
|
||||
})();
|
||||
});
|
||||
|
||||
addReducer('loadChatInviteRequesters', (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,
|
||||
isRequested: true,
|
||||
});
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
global = getGlobal();
|
||||
const currentInviteInfo = global.management.byChatId[chatId]?.inviteInfo;
|
||||
if (!currentInviteInfo?.invite || currentInviteInfo.invite.link !== link) return;
|
||||
setGlobal(updateManagement(global, chatId, {
|
||||
inviteInfo: {
|
||||
...currentInviteInfo,
|
||||
requesters: result,
|
||||
},
|
||||
}));
|
||||
})();
|
||||
});
|
||||
|
||||
addReducer('loadChatJoinRequests', (global, actions, payload) => {
|
||||
const {
|
||||
chatId, offsetDate, offsetUserId, limit,
|
||||
|
||||
@ -293,6 +293,7 @@ export interface ManagementState {
|
||||
inviteInfo?: {
|
||||
invite: ApiExportedInvite;
|
||||
importers?: ApiChatInviteImporter[];
|
||||
requesters?: ApiChatInviteImporter[];
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user