feat: adds info for private communities
parent
8a1d1b7661
commit
fff7626ae6
|
@ -2,9 +2,10 @@
|
|||
"src/components/views/auth/AuthFooter.tsx": "src/components/views/auth/VectorAuthFooter.tsx",
|
||||
"src/components/views/auth/AuthHeaderLogo.tsx": "src/components/views/auth/VectorAuthHeaderLogo.tsx",
|
||||
"src/components/views/auth/AuthPage.tsx": "src/components/views/auth/VectorAuthPage.tsx",
|
||||
"src/components/views/rooms/RoomTile.tsx": "src/components/views/rooms/RoomTile.tsx",
|
||||
"src/components/views/rooms/NewRoomIntro.tsx": "src/components/views/rooms/NewRoomIntro.tsx",
|
||||
"src/components/views/rooms/MessageComposer.tsx": "src/components/views/rooms/MessageComposer.tsx",
|
||||
"src/components/views/rooms/RoomTile.tsx": "src/components/views/rooms/RoomTile.tsx",
|
||||
"src/components/views/rooms/RoomPreviewBar.tsx": "src/components/views/rooms/RoomPreviewBar.tsx",
|
||||
"src/components/views/rooms/NewRoomIntro.tsx": "src/components/views/rooms/NewRoomIntro.tsx",
|
||||
"src/components/views/elements/RoomName.tsx": "src/components/views/elements/RoomName.tsx",
|
||||
"src/components/views/dialogs/spotlight/PublicRoomResultDetails.tsx": "src/components/views/dialogs/spotlight/PublicRoomResultDetails.tsx",
|
||||
"src/components/views/avatars/BaseAvatar.tsx": "src/components/views/avatars/BaseAvatar.tsx",
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
import { useAtom } from "jotai";
|
||||
import { minimumTokenThresholdAtom } from "../../../atoms";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import React, { ReactElement } from "react";
|
||||
import { cleanRoomName } from "../../../hooks/useVerifiedRoom";
|
||||
|
||||
export function CommunityRoomPeekMessage({ roomName }: { roomName: string }): ReactElement {
|
||||
const [allTokens] = useAtom(minimumTokenThresholdAtom)
|
||||
const cleanedRoomName = cleanRoomName(roomName);
|
||||
|
||||
let tokenThreshold = allTokens[cleanedRoomName];
|
||||
|
||||
return (
|
||||
<h3>{_t("room|no_peek_join_prompt_community", { roomName: cleanedRoomName })} {
|
||||
tokenThreshold ? (_t('room|no_peek_join_prompt_community_threshold', tokenThreshold)) : ''
|
||||
}</h3>
|
||||
);
|
||||
}
|
|
@ -1,14 +1,13 @@
|
|||
import { useAtom } from "jotai";
|
||||
import { communityBotAtom, minimumTokenThresholdAtom } from "../../../atoms";
|
||||
import { minimumTokenThresholdAtom } from "../../../atoms";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import React from "react";
|
||||
import { useVerifiedRoom } from "../../../hooks/useVerifiedRoom";
|
||||
import { Room } from "matrix-js-sdk/src/matrix";
|
||||
import { MessageButton } from "./MessageButton";
|
||||
import { MessageCommunityBotButton } from "./MessageButton";
|
||||
|
||||
export function DisabledMessageField({ room }: { room: Room }): JSX.Element {
|
||||
const [allTokens] = useAtom(minimumTokenThresholdAtom)
|
||||
const [communityBot] = useAtom(communityBotAtom)
|
||||
const { isTokenGatedRoom, isCommunityRoom, } = useVerifiedRoom(room);
|
||||
|
||||
let tokenThreshold = allTokens[room.name];
|
||||
|
@ -26,11 +25,11 @@ export function DisabledMessageField({ room }: { room: Room }): JSX.Element {
|
|||
if (tokenThreshold) {
|
||||
return (
|
||||
<div key="controls_error" className="mx_MessageComposer_noperm_error">
|
||||
You need at least {tokenThreshold.threshold} {tokenThreshold.symbol} to join this
|
||||
community.{ isCommunityRoom ? (
|
||||
{_t("composer|no_perms_token_notice", tokenThreshold)}
|
||||
{ isCommunityRoom ? (
|
||||
<>
|
||||
<span style={{'marginLeft': '1rem', display: 'block'}}></span>
|
||||
<MessageButton text={'Get room tokens'} member={communityBot}></MessageButton>
|
||||
<MessageCommunityBotButton text={'Get room tokens'} />
|
||||
</>
|
||||
) : null }
|
||||
</div>
|
||||
|
|
|
@ -6,7 +6,8 @@ import { Icon as SendMessage } from "../../../../res/themes/superhero/img/icons/
|
|||
import { MatrixClient, RoomMember, User } from "matrix-js-sdk/src/matrix";
|
||||
import { DirectoryMember, startDmOnFirstMessage } from "matrix-react-sdk/src/utils/direct-messages";
|
||||
|
||||
import { BareUser } from "../../../atoms";
|
||||
import { BareUser, communityBotAtom } from "../../../atoms";
|
||||
import { useAtom } from "jotai";
|
||||
/**
|
||||
* Converts the member to a DirectoryMember and starts a DM with them.
|
||||
*/
|
||||
|
@ -41,3 +42,13 @@ export const MessageButton = ({ member, text = 'Send Message' }: { member: Membe
|
|||
</AccessibleButton>
|
||||
);
|
||||
};
|
||||
|
||||
export const MessageCommunityBotButton = ({ text = 'Send Message' }: { text?: string }): JSX.Element => {
|
||||
const [communityBot] = useAtom(communityBotAtom)
|
||||
|
||||
return (
|
||||
<MessageButton member={communityBot} text={text} />
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,753 @@
|
|||
/*
|
||||
Copyright 2015-2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { ChangeEvent, ReactNode } from "react";
|
||||
import {
|
||||
Room,
|
||||
RoomMember,
|
||||
EventType,
|
||||
RoomType,
|
||||
IJoinRuleEventContent,
|
||||
JoinRule,
|
||||
MatrixError,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import classNames from "classnames";
|
||||
import { RoomPreviewOpts, RoomViewLifecycle } from "@matrix-org/react-sdk-module-api/lib/lifecycles/RoomViewLifecycle";
|
||||
|
||||
import { Icon as AskToJoinIcon } from "matrix-react-sdk/res/img/element-icons/ask-to-join.svg";
|
||||
import { IOOBData } from "matrix-react-sdk/src/stores/ThreepidInviteStore";
|
||||
import { MatrixClientPeg } from "matrix-react-sdk/src/MatrixClientPeg";
|
||||
import IdentityAuthClient from "matrix-react-sdk/src/IdentityAuthClient";
|
||||
import { UserFriendlyError } from "matrix-react-sdk/src/languageHandler";
|
||||
import SdkConfig from "matrix-react-sdk/src/SdkConfig";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { ModuleRunner } from "matrix-react-sdk/src/modules/ModuleRunner";
|
||||
import SettingsStore from "matrix-react-sdk/src/settings/SettingsStore";
|
||||
import { UIFeature } from "matrix-react-sdk/src/settings/UIFeature";
|
||||
import Spinner from "matrix-react-sdk/src/components/views/elements/Spinner";
|
||||
import RoomAvatar from "matrix-react-sdk/src/components/views/avatars/RoomAvatar";
|
||||
import InviteReason from "matrix-react-sdk/src/components/views/elements/InviteReason";
|
||||
import AccessibleButton from "matrix-react-sdk/src/components/views/elements/AccessibleButton";
|
||||
import Field from "matrix-react-sdk/src/components/views/elements/Field";
|
||||
import dis from "matrix-react-sdk/src/dispatcher/dispatcher";
|
||||
import { isVerifiedRoom } from "../../../hooks/useVerifiedRoom";
|
||||
import { MessageCommunityBotButton } from "../elements/MessageButton";
|
||||
import { CommunityRoomPeekMessage } from "../elements/CommunityRoomPeekMessage";
|
||||
|
||||
const MemberEventHtmlReasonField = "io.element.html_reason";
|
||||
|
||||
enum MessageCase {
|
||||
NotLoggedIn = "NotLoggedIn",
|
||||
Joining = "Joining",
|
||||
Loading = "Loading",
|
||||
Rejecting = "Rejecting",
|
||||
Kicked = "Kicked",
|
||||
Banned = "Banned",
|
||||
OtherThreePIDError = "OtherThreePIDError",
|
||||
InvitedEmailNotFoundInAccount = "InvitedEmailNotFoundInAccount",
|
||||
InvitedEmailNoIdentityServer = "InvitedEmailNoIdentityServer",
|
||||
InvitedEmailMismatch = "InvitedEmailMismatch",
|
||||
Invite = "Invite",
|
||||
ViewingRoom = "ViewingRoom",
|
||||
RoomNotFound = "RoomNotFound",
|
||||
OtherError = "OtherError",
|
||||
PromptAskToJoin = "PromptAskToJoin",
|
||||
Knocked = "Knocked",
|
||||
RequestDenied = "requestDenied",
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
// if inviterName is specified, the preview bar will shown an invite to the room.
|
||||
// You should also specify onRejectClick if specifying inviterName
|
||||
inviterName?: string;
|
||||
|
||||
// If invited by 3rd party invite, the email address the invite was sent to
|
||||
invitedEmail?: string;
|
||||
|
||||
// For third party invites, information passed about the room out-of-band
|
||||
oobData?: IOOBData;
|
||||
|
||||
// For third party invites, a URL for a 3pid invite signing service
|
||||
signUrl?: string;
|
||||
|
||||
// A standard client/server API error object. If supplied, indicates that the
|
||||
// caller was unable to fetch details about the room for the given reason.
|
||||
error?: MatrixError;
|
||||
|
||||
canPreview?: boolean;
|
||||
previewLoading?: boolean;
|
||||
|
||||
// The id of the room to be previewed, if it is known.
|
||||
// (It may be unknown if we are waiting for an alias to be resolved.)
|
||||
roomId?: string;
|
||||
|
||||
// A `Room` object for the room to be previewed, if we have one.
|
||||
room?: Room;
|
||||
|
||||
loading?: boolean;
|
||||
joining?: boolean;
|
||||
rejecting?: boolean;
|
||||
// The alias that was used to access this room, if appropriate
|
||||
// If given, this will be how the room is referred to (eg.
|
||||
// in error messages).
|
||||
roomAlias?: string;
|
||||
|
||||
onJoinClick?(): void;
|
||||
onRejectClick?(): void;
|
||||
onRejectAndIgnoreClick?(): void;
|
||||
onForgetClick?(): void;
|
||||
|
||||
canAskToJoinAndMembershipIsLeave?: boolean;
|
||||
promptAskToJoin?: boolean;
|
||||
knocked?: boolean;
|
||||
onSubmitAskToJoin?(reason?: string): void;
|
||||
onCancelAskToJoin?(): void;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
busy: boolean;
|
||||
accountEmails?: string[];
|
||||
invitedEmailMxid?: string;
|
||||
threePidFetchError?: MatrixError;
|
||||
reason?: string;
|
||||
}
|
||||
|
||||
export default class RoomPreviewBar extends React.Component<IProps, IState> {
|
||||
public static defaultProps = {
|
||||
onJoinClick() {},
|
||||
};
|
||||
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
busy: false,
|
||||
};
|
||||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
this.checkInvitedEmail();
|
||||
}
|
||||
|
||||
public componentDidUpdate(prevProps: IProps, prevState: IState): void {
|
||||
if (this.props.invitedEmail !== prevProps.invitedEmail || this.props.inviterName !== prevProps.inviterName) {
|
||||
this.checkInvitedEmail();
|
||||
}
|
||||
}
|
||||
|
||||
private async checkInvitedEmail(): Promise<void> {
|
||||
// If this is an invite and we've been told what email address was
|
||||
// invited, fetch the user's account emails and discovery bindings so we
|
||||
// can check them against the email that was invited.
|
||||
if (this.props.inviterName && this.props.invitedEmail) {
|
||||
this.setState({ busy: true });
|
||||
try {
|
||||
// Gather the account 3PIDs
|
||||
const account3pids = await MatrixClientPeg.safeGet().getThreePids();
|
||||
this.setState({
|
||||
accountEmails: account3pids.threepids.filter((b) => b.medium === "email").map((b) => b.address),
|
||||
});
|
||||
// If we have an IS connected, use that to lookup the email and
|
||||
// check the bound MXID.
|
||||
if (!MatrixClientPeg.safeGet().getIdentityServerUrl()) {
|
||||
this.setState({ busy: false });
|
||||
return;
|
||||
}
|
||||
const authClient = new IdentityAuthClient();
|
||||
const identityAccessToken = await authClient.getAccessToken();
|
||||
const result = await MatrixClientPeg.safeGet().lookupThreePid(
|
||||
"email",
|
||||
this.props.invitedEmail,
|
||||
identityAccessToken!,
|
||||
);
|
||||
if (!("mxid" in result)) {
|
||||
throw new UserFriendlyError("room|error_3pid_invite_email_lookup");
|
||||
}
|
||||
this.setState({ invitedEmailMxid: result.mxid });
|
||||
} catch (err) {
|
||||
this.setState({ threePidFetchError: err as MatrixError });
|
||||
}
|
||||
this.setState({ busy: false });
|
||||
}
|
||||
}
|
||||
|
||||
private getMessageCase(): MessageCase {
|
||||
const isGuest = MatrixClientPeg.safeGet().isGuest();
|
||||
|
||||
if (isGuest) {
|
||||
return MessageCase.NotLoggedIn;
|
||||
}
|
||||
|
||||
const myMember = this.getMyMember();
|
||||
|
||||
if (myMember) {
|
||||
const previousMembership = myMember.events.member?.getPrevContent().membership;
|
||||
if (myMember.isKicked()) {
|
||||
if (previousMembership === "knock") {
|
||||
return MessageCase.RequestDenied;
|
||||
} else if (this.props.promptAskToJoin) {
|
||||
return MessageCase.PromptAskToJoin;
|
||||
}
|
||||
return MessageCase.Kicked;
|
||||
} else if (myMember.membership === "ban") {
|
||||
return MessageCase.Banned;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.props.joining) {
|
||||
return MessageCase.Joining;
|
||||
} else if (this.props.rejecting) {
|
||||
return MessageCase.Rejecting;
|
||||
} else if (this.props.loading || this.state.busy) {
|
||||
return MessageCase.Loading;
|
||||
} else if (this.props.knocked) {
|
||||
return MessageCase.Knocked;
|
||||
} else if (this.props.canAskToJoinAndMembershipIsLeave || this.props.promptAskToJoin) {
|
||||
return MessageCase.PromptAskToJoin;
|
||||
}
|
||||
|
||||
if (this.props.inviterName) {
|
||||
if (this.props.invitedEmail) {
|
||||
if (this.state.threePidFetchError) {
|
||||
return MessageCase.OtherThreePIDError;
|
||||
} else if (this.state.accountEmails && !this.state.accountEmails.includes(this.props.invitedEmail)) {
|
||||
return MessageCase.InvitedEmailNotFoundInAccount;
|
||||
} else if (!MatrixClientPeg.safeGet().getIdentityServerUrl()) {
|
||||
return MessageCase.InvitedEmailNoIdentityServer;
|
||||
} else if (this.state.invitedEmailMxid != MatrixClientPeg.safeGet().getUserId()) {
|
||||
return MessageCase.InvitedEmailMismatch;
|
||||
}
|
||||
}
|
||||
return MessageCase.Invite;
|
||||
} else if (this.props.error) {
|
||||
if ((this.props.error as MatrixError).errcode == "M_NOT_FOUND") {
|
||||
return MessageCase.RoomNotFound;
|
||||
} else {
|
||||
return MessageCase.OtherError;
|
||||
}
|
||||
} else {
|
||||
return MessageCase.ViewingRoom;
|
||||
}
|
||||
}
|
||||
|
||||
private getKickOrBanInfo(): { memberName?: string; reason?: string } {
|
||||
const myMember = this.getMyMember();
|
||||
if (!myMember) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const kickerUserId = myMember.events.member?.getSender();
|
||||
const kickerMember = kickerUserId ? this.props.room?.currentState.getMember(kickerUserId) : undefined;
|
||||
const memberName = kickerMember?.name ?? kickerUserId;
|
||||
const reason = myMember.events.member?.getContent().reason;
|
||||
return { memberName, reason };
|
||||
}
|
||||
|
||||
private joinRule(): JoinRule | null {
|
||||
return (
|
||||
this.props.room?.currentState
|
||||
.getStateEvents(EventType.RoomJoinRules, "")
|
||||
?.getContent<IJoinRuleEventContent>().join_rule ?? null
|
||||
);
|
||||
}
|
||||
|
||||
private getMyMember(): RoomMember | null {
|
||||
return this.props.room?.getMember(MatrixClientPeg.safeGet().getSafeUserId()) ?? null;
|
||||
}
|
||||
|
||||
private getInviteMember(): RoomMember | null {
|
||||
const { room } = this.props;
|
||||
if (!room) {
|
||||
return null;
|
||||
}
|
||||
const myUserId = MatrixClientPeg.safeGet().getSafeUserId();
|
||||
const inviteEvent = room.currentState.getMember(myUserId);
|
||||
if (!inviteEvent) {
|
||||
return null;
|
||||
}
|
||||
const inviterUserId = inviteEvent.events.member?.getSender();
|
||||
return inviterUserId ? room.currentState.getMember(inviterUserId) : null;
|
||||
}
|
||||
|
||||
private isDMInvite(): boolean {
|
||||
const myMember = this.getMyMember();
|
||||
if (!myMember) {
|
||||
return false;
|
||||
}
|
||||
const memberContent = myMember.events.member?.getContent();
|
||||
return memberContent?.membership === "invite" && memberContent.is_direct;
|
||||
}
|
||||
|
||||
private makeScreenAfterLogin(): { screen: string; params: Record<string, any> } {
|
||||
return {
|
||||
screen: "room",
|
||||
params: {
|
||||
email: this.props.invitedEmail,
|
||||
signurl: this.props.signUrl,
|
||||
room_name: this.props.oobData?.name ?? null,
|
||||
room_avatar_url: this.props.oobData?.avatarUrl ?? null,
|
||||
inviter_name: this.props.oobData?.inviterName ?? null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private onLoginClick = (): void => {
|
||||
dis.dispatch({ action: "start_login", screenAfterLogin: this.makeScreenAfterLogin() });
|
||||
};
|
||||
|
||||
private onRegisterClick = (): void => {
|
||||
dis.dispatch({ action: "start_registration", screenAfterLogin: this.makeScreenAfterLogin() });
|
||||
};
|
||||
|
||||
private onChangeReason = (event: ChangeEvent<HTMLTextAreaElement>): void => {
|
||||
this.setState({ reason: event.target.value });
|
||||
};
|
||||
|
||||
public render(): React.ReactNode {
|
||||
const brand = SdkConfig.get().brand;
|
||||
const roomName = this.props.room?.name ?? this.props.roomAlias ?? "";
|
||||
const isSpace = this.props.room?.isSpaceRoom() ?? this.props.oobData?.roomType === RoomType.Space;
|
||||
|
||||
let showSpinner = false;
|
||||
let title: string | undefined;
|
||||
let subTitle: string | ReactNode[] | undefined;
|
||||
let reasonElement: JSX.Element | undefined;
|
||||
let primaryActionHandler: (() => void) | undefined;
|
||||
let primaryActionLabel: string | undefined;
|
||||
let secondaryActionHandler: (() => void) | undefined;
|
||||
let secondaryActionLabel: string | undefined;
|
||||
let footer: JSX.Element | undefined;
|
||||
const extraComponents: JSX.Element[] = [];
|
||||
|
||||
const { isCommunityRoom } = isVerifiedRoom(roomName);
|
||||
|
||||
|
||||
const messageCase = this.getMessageCase();
|
||||
switch (messageCase) {
|
||||
case MessageCase.Joining: {
|
||||
if (this.props.oobData?.roomType || isSpace) {
|
||||
title = isSpace ? _t("room|joining_space") : _t("room|joining_room");
|
||||
} else {
|
||||
title = _t("room|joining");
|
||||
}
|
||||
|
||||
showSpinner = true;
|
||||
break;
|
||||
}
|
||||
case MessageCase.Loading: {
|
||||
title = _t("common|loading");
|
||||
showSpinner = true;
|
||||
break;
|
||||
}
|
||||
case MessageCase.Rejecting: {
|
||||
title = _t("room|rejecting");
|
||||
showSpinner = true;
|
||||
break;
|
||||
}
|
||||
case MessageCase.NotLoggedIn: {
|
||||
const opts: RoomPreviewOpts = { canJoin: false };
|
||||
if (this.props.roomId) {
|
||||
ModuleRunner.instance.invoke(RoomViewLifecycle.PreviewRoomNotLoggedIn, opts, this.props.roomId);
|
||||
}
|
||||
if (opts.canJoin) {
|
||||
title = _t("room|join_title");
|
||||
primaryActionLabel = _t("action|join");
|
||||
primaryActionHandler = () => {
|
||||
ModuleRunner.instance.invoke(RoomViewLifecycle.JoinFromRoomPreview, this.props.roomId);
|
||||
};
|
||||
} else {
|
||||
title = _t("room|join_title_account");
|
||||
if (SettingsStore.getValue(UIFeature.Registration)) {
|
||||
primaryActionLabel = _t("room|join_button_account");
|
||||
primaryActionHandler = this.onRegisterClick;
|
||||
}
|
||||
secondaryActionLabel = _t("action|sign_in");
|
||||
secondaryActionHandler = this.onLoginClick;
|
||||
}
|
||||
if (this.props.previewLoading) {
|
||||
footer = (
|
||||
<div>
|
||||
<Spinner w={20} h={20} />
|
||||
{_t("room|loading_preview")}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MessageCase.Kicked: {
|
||||
const { memberName, reason } = this.getKickOrBanInfo();
|
||||
if (roomName) {
|
||||
title = _t("room|kicked_from_room_by", { memberName, roomName });
|
||||
} else {
|
||||
title = _t("room|kicked_by", { memberName });
|
||||
}
|
||||
subTitle = reason ? _t("room|kick_reason", { reason }) : undefined;
|
||||
|
||||
if (isSpace) {
|
||||
primaryActionLabel = _t("room|forget_space");
|
||||
} else {
|
||||
primaryActionLabel = _t("room|forget_room");
|
||||
}
|
||||
primaryActionHandler = this.props.onForgetClick;
|
||||
|
||||
if (this.joinRule() !== JoinRule.Invite) {
|
||||
secondaryActionLabel = primaryActionLabel;
|
||||
secondaryActionHandler = primaryActionHandler;
|
||||
|
||||
primaryActionLabel = _t("room|rejoin_button");
|
||||
primaryActionHandler = this.props.onJoinClick;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MessageCase.RequestDenied: {
|
||||
title = _t("room|knock_denied_title");
|
||||
|
||||
subTitle = _t("room|knock_denied_subtitle");
|
||||
|
||||
if (isSpace) {
|
||||
primaryActionLabel = _t("room|forget_space");
|
||||
} else {
|
||||
primaryActionLabel = _t("room|forget_room");
|
||||
}
|
||||
primaryActionHandler = this.props.onForgetClick;
|
||||
break;
|
||||
}
|
||||
case MessageCase.Banned: {
|
||||
const { memberName, reason } = this.getKickOrBanInfo();
|
||||
if (roomName) {
|
||||
title = _t("room|banned_from_room_by", { memberName, roomName });
|
||||
} else {
|
||||
title = _t("room|banned_by", { memberName });
|
||||
}
|
||||
subTitle = reason ? _t("room|kick_reason", { reason }) : undefined;
|
||||
if (isSpace) {
|
||||
primaryActionLabel = _t("room|forget_space");
|
||||
} else {
|
||||
primaryActionLabel = _t("room|forget_room");
|
||||
}
|
||||
primaryActionHandler = this.props.onForgetClick;
|
||||
break;
|
||||
}
|
||||
case MessageCase.OtherThreePIDError: {
|
||||
if (roomName) {
|
||||
title = _t("room|3pid_invite_error_title_room", { roomName });
|
||||
} else {
|
||||
title = _t("room|3pid_invite_error_title");
|
||||
}
|
||||
const joinRule = this.joinRule();
|
||||
const errCodeMessage = _t("room|3pid_invite_error_description", {
|
||||
errcode: this.state.threePidFetchError?.errcode || _t("error|unknown_error_code"),
|
||||
});
|
||||
switch (joinRule) {
|
||||
case "invite":
|
||||
subTitle = [_t("room|3pid_invite_error_invite_subtitle"), errCodeMessage];
|
||||
primaryActionLabel = _t("room|3pid_invite_error_invite_action");
|
||||
primaryActionHandler = this.props.onJoinClick;
|
||||
break;
|
||||
case "public":
|
||||
subTitle = _t("room|3pid_invite_error_public_subtitle");
|
||||
primaryActionLabel = _t("room|join_the_discussion");
|
||||
primaryActionHandler = this.props.onJoinClick;
|
||||
break;
|
||||
default:
|
||||
subTitle = errCodeMessage;
|
||||
primaryActionLabel = _t("room|3pid_invite_error_invite_action");
|
||||
primaryActionHandler = this.props.onJoinClick;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MessageCase.InvitedEmailNotFoundInAccount: {
|
||||
if (roomName) {
|
||||
title = _t("room|3pid_invite_email_not_found_account_room", {
|
||||
roomName,
|
||||
email: this.props.invitedEmail,
|
||||
});
|
||||
} else {
|
||||
title = _t("room|3pid_invite_email_not_found_account", {
|
||||
email: this.props.invitedEmail,
|
||||
});
|
||||
}
|
||||
|
||||
subTitle = _t("room|link_email_to_receive_3pid_invite", { brand });
|
||||
primaryActionLabel = _t("room|join_the_discussion");
|
||||
primaryActionHandler = this.props.onJoinClick;
|
||||
break;
|
||||
}
|
||||
case MessageCase.InvitedEmailNoIdentityServer: {
|
||||
if (roomName) {
|
||||
title = _t("room|invite_sent_to_email_room", {
|
||||
roomName,
|
||||
email: this.props.invitedEmail,
|
||||
});
|
||||
} else {
|
||||
title = _t("room|invite_sent_to_email", { email: this.props.invitedEmail });
|
||||
}
|
||||
|
||||
subTitle = _t("room|3pid_invite_no_is_subtitle", {
|
||||
brand,
|
||||
});
|
||||
primaryActionLabel = _t("room|join_the_discussion");
|
||||
primaryActionHandler = this.props.onJoinClick;
|
||||
break;
|
||||
}
|
||||
case MessageCase.InvitedEmailMismatch: {
|
||||
if (roomName) {
|
||||
title = _t("room|invite_sent_to_email_room", {
|
||||
roomName,
|
||||
email: this.props.invitedEmail,
|
||||
});
|
||||
} else {
|
||||
title = _t("room|invite_sent_to_email", { email: this.props.invitedEmail });
|
||||
}
|
||||
|
||||
subTitle = _t("room|invite_email_mismatch_suggestion", { brand });
|
||||
primaryActionLabel = _t("room|join_the_discussion");
|
||||
primaryActionHandler = this.props.onJoinClick;
|
||||
break;
|
||||
}
|
||||
case MessageCase.Invite: {
|
||||
const avatar = <RoomAvatar room={this.props.room} oobData={this.props.oobData} />;
|
||||
|
||||
const inviteMember = this.getInviteMember();
|
||||
let inviterElement: JSX.Element;
|
||||
if (inviteMember) {
|
||||
inviterElement = (
|
||||
<span>
|
||||
<span className="mx_RoomPreviewBar_inviter">{inviteMember.rawDisplayName}</span> (
|
||||
{inviteMember.userId})
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
inviterElement = <span className="mx_RoomPreviewBar_inviter">{this.props.inviterName}</span>;
|
||||
}
|
||||
|
||||
const isDM = this.isDMInvite();
|
||||
if (isDM) {
|
||||
title = _t("room|dm_invite_title", {
|
||||
user: inviteMember?.name ?? this.props.inviterName,
|
||||
});
|
||||
subTitle = [avatar, _t("room|dm_invite_subtitle", {}, { userName: () => inviterElement })];
|
||||
primaryActionLabel = _t("room|dm_invite_action");
|
||||
} else {
|
||||
title = _t("room|invite_title", { roomName });
|
||||
subTitle = [avatar, _t("room|invite_subtitle", {}, { userName: () => inviterElement })];
|
||||
primaryActionLabel = _t("action|accept");
|
||||
}
|
||||
|
||||
const myUserId = MatrixClientPeg.safeGet().getSafeUserId();
|
||||
const member = this.props.room?.currentState.getMember(myUserId);
|
||||
const memberEventContent = member?.events.member?.getContent();
|
||||
|
||||
if (memberEventContent?.reason) {
|
||||
reasonElement = (
|
||||
<InviteReason
|
||||
reason={memberEventContent.reason}
|
||||
htmlReason={memberEventContent[MemberEventHtmlReasonField]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
primaryActionHandler = this.props.onJoinClick;
|
||||
secondaryActionLabel = _t("action|reject");
|
||||
secondaryActionHandler = this.props.onRejectClick;
|
||||
|
||||
if (this.props.onRejectAndIgnoreClick) {
|
||||
extraComponents.push(
|
||||
<AccessibleButton kind="secondary" onClick={this.props.onRejectAndIgnoreClick} key="ignore">
|
||||
{_t("room|invite_reject_ignore")}
|
||||
</AccessibleButton>,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MessageCase.ViewingRoom: {
|
||||
if (this.props.canPreview) {
|
||||
title = _t("room|peek_join_prompt", { roomName });
|
||||
} else if (roomName) {
|
||||
title = _t("room|no_peek_join_prompt", { roomName });
|
||||
} else {
|
||||
title = _t("room|no_peek_no_name_join_prompt");
|
||||
}
|
||||
primaryActionLabel = _t("room|join_the_discussion");
|
||||
primaryActionHandler = this.props.onJoinClick;
|
||||
break;
|
||||
}
|
||||
case MessageCase.RoomNotFound: {
|
||||
if (roomName) {
|
||||
title = _t("room|not_found_title_name", { roomName });
|
||||
} else {
|
||||
title = _t("room|not_found_title");
|
||||
}
|
||||
subTitle = _t("room|not_found_subtitle");
|
||||
break;
|
||||
}
|
||||
case MessageCase.OtherError: {
|
||||
if (roomName) {
|
||||
title = _t("room|inaccessible_name", { roomName });
|
||||
} else {
|
||||
title = _t("room|inaccessible");
|
||||
}
|
||||
subTitle = [
|
||||
_t("room|inaccessible_subtitle_1"),
|
||||
_t(
|
||||
"room|inaccessible_subtitle_2",
|
||||
{ errcode: String(this.props.error?.errcode) },
|
||||
{
|
||||
issueLink: (label) => (
|
||||
<a
|
||||
href={SdkConfig.get().feedback.new_issue_url}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
{label}
|
||||
</a>
|
||||
),
|
||||
},
|
||||
),
|
||||
];
|
||||
break;
|
||||
}
|
||||
case MessageCase.PromptAskToJoin: {
|
||||
if (roomName) {
|
||||
title = _t("room|knock_prompt_name", { roomName });
|
||||
} else {
|
||||
title = _t("room|knock_prompt");
|
||||
}
|
||||
|
||||
const avatar = <RoomAvatar room={this.props.room} oobData={this.props.oobData} />;
|
||||
subTitle = [avatar, _t("room|knock_subtitle")];
|
||||
|
||||
reasonElement = (
|
||||
<Field
|
||||
autoFocus
|
||||
className="mx_RoomPreviewBar_fullWidth"
|
||||
element="textarea"
|
||||
onChange={this.onChangeReason}
|
||||
placeholder={_t("room|knock_message_field_placeholder")}
|
||||
type="text"
|
||||
value={this.state.reason ?? ""}
|
||||
/>
|
||||
);
|
||||
|
||||
primaryActionHandler = () =>
|
||||
this.props.onSubmitAskToJoin && this.props.onSubmitAskToJoin(this.state.reason);
|
||||
primaryActionLabel = _t("room|knock_send_action");
|
||||
|
||||
break;
|
||||
}
|
||||
case MessageCase.Knocked: {
|
||||
title = _t("room|knock_sent");
|
||||
|
||||
subTitle = [
|
||||
<>
|
||||
<AskToJoinIcon className="mx_Icon mx_Icon_16 mx_RoomPreviewBar_icon" />
|
||||
{_t("room|knock_sent_subtitle")}
|
||||
</>,
|
||||
];
|
||||
|
||||
secondaryActionHandler = this.props.onCancelAskToJoin;
|
||||
secondaryActionLabel = _t("room|knock_cancel_action");
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let subTitleElements;
|
||||
if (subTitle) {
|
||||
if (!Array.isArray(subTitle)) {
|
||||
subTitle = [subTitle];
|
||||
}
|
||||
subTitleElements = subTitle.map((t, i) => <p key={`subTitle${i}`}>{t}</p>);
|
||||
}
|
||||
|
||||
let titleElement;
|
||||
if (showSpinner) {
|
||||
titleElement = (
|
||||
<h3 className="mx_RoomPreviewBar_spinnerTitle">
|
||||
<Spinner />
|
||||
{title}
|
||||
</h3>
|
||||
);
|
||||
} else {
|
||||
titleElement = <h3>{title}</h3>;
|
||||
}
|
||||
|
||||
let primaryButton;
|
||||
if (primaryActionHandler) {
|
||||
primaryButton = (
|
||||
<AccessibleButton kind="primary" onClick={primaryActionHandler}>
|
||||
{primaryActionLabel}
|
||||
</AccessibleButton>
|
||||
);
|
||||
}
|
||||
|
||||
let secondaryButton;
|
||||
if (secondaryActionHandler) {
|
||||
secondaryButton = (
|
||||
<AccessibleButton kind="secondary" onClick={secondaryActionHandler}>
|
||||
{secondaryActionLabel}
|
||||
</AccessibleButton>
|
||||
);
|
||||
}
|
||||
|
||||
if(isCommunityRoom) {
|
||||
secondaryButton = primaryButton;
|
||||
primaryButton = (<MessageCommunityBotButton text={'Message Superhero Bot to get tokens'} />);
|
||||
titleElement = (<CommunityRoomPeekMessage roomName={roomName} />);
|
||||
}
|
||||
|
||||
const isPanel = this.props.canPreview;
|
||||
|
||||
const classes = classNames("mx_RoomPreviewBar", `mx_RoomPreviewBar_${messageCase}`, {
|
||||
mx_RoomPreviewBar_panel: isPanel,
|
||||
mx_RoomPreviewBar_dialog: !isPanel,
|
||||
});
|
||||
|
||||
// ensure correct tab order for both views
|
||||
const actions = isPanel ? (
|
||||
<>
|
||||
{secondaryButton}
|
||||
{extraComponents}
|
||||
{primaryButton}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{primaryButton}
|
||||
{extraComponents}
|
||||
{secondaryButton}
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<div role="complementary" className={classes}>
|
||||
<div className="mx_RoomPreviewBar_message">
|
||||
{titleElement}
|
||||
{subTitleElements}
|
||||
</div>
|
||||
{reasonElement}
|
||||
<div
|
||||
className={classNames("mx_RoomPreviewBar_actions", {
|
||||
mx_RoomPreviewBar_fullWidth: messageCase === MessageCase.PromptAskToJoin,
|
||||
})}
|
||||
>
|
||||
{actions}
|
||||
</div>
|
||||
<div className="mx_RoomPreviewBar_footer">{footer}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -22,3 +22,21 @@ export function useVerifiedRoom(room?: Room | IPublicRoomsChunkRoom): {
|
|||
isCommunityRoom,
|
||||
};
|
||||
}
|
||||
|
||||
export function cleanRoomName(roomName: string) {
|
||||
// remove # in the beginning
|
||||
let parsedName = roomName.startsWith('#') ? roomName.slice(1) : roomName;
|
||||
|
||||
// remove domain
|
||||
parsedName = parsedName.split(':')[0];
|
||||
|
||||
return parsedName;
|
||||
}
|
||||
|
||||
export function isVerifiedRoom(roomName: string) {
|
||||
let parsedRoomName = cleanRoomName(roomName);
|
||||
return {
|
||||
isTokenGatedRoom: parsedRoomName.startsWith("[TG]"),
|
||||
isCommunityRoom: parsedRoomName.startsWith("$"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,6 +40,10 @@
|
|||
"superhero_dex": "Superhero DEX",
|
||||
"mint_a_token": "Mint a token",
|
||||
"composer": {
|
||||
"no_perms_notice": "You need to own the room token to be able to post in this room."
|
||||
"no_perms_token_notice": "You need to own more than ~ %(threshold)s %(symbol)s tokens to be able to post in this room."
|
||||
},
|
||||
"room": {
|
||||
"no_peek_join_prompt_community": "%(roomName)s is a private token-gated room.",
|
||||
"no_peek_join_prompt_community_threshold": "You need to own %(threshold)s %(symbol)s tokens to access it."
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue