diff --git a/components.json b/components.json
index bbf8fea8a9..3c34db1d97 100644
--- a/components.json
+++ b/components.json
@@ -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",
diff --git a/src/components/views/elements/CommunityRoomPeekMessage.tsx b/src/components/views/elements/CommunityRoomPeekMessage.tsx
new file mode 100644
index 0000000000..c8664590e9
--- /dev/null
+++ b/src/components/views/elements/CommunityRoomPeekMessage.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 (
+
{_t("room|no_peek_join_prompt_community", { roomName: cleanedRoomName })} {
+ tokenThreshold ? (_t('room|no_peek_join_prompt_community_threshold', tokenThreshold)) : ''
+ }
+ );
+}
diff --git a/src/components/views/elements/DisabledMessageField.tsx b/src/components/views/elements/DisabledMessageField.tsx
index 73ea565472..6c29bdabed 100644
--- a/src/components/views/elements/DisabledMessageField.tsx
+++ b/src/components/views/elements/DisabledMessageField.tsx
@@ -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 (
- You need at least {tokenThreshold.threshold} {tokenThreshold.symbol} to join this
- community.{ isCommunityRoom ? (
+ {_t("composer|no_perms_token_notice", tokenThreshold)}
+ { isCommunityRoom ? (
<>
-
+
>
) : null }
diff --git a/src/components/views/elements/MessageButton.tsx b/src/components/views/elements/MessageButton.tsx
index 15ba43997b..1981342aee 100644
--- a/src/components/views/elements/MessageButton.tsx
+++ b/src/components/views/elements/MessageButton.tsx
@@ -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
);
};
+
+export const MessageCommunityBotButton = ({ text = 'Send Message' }: { text?: string }): JSX.Element => {
+ const [communityBot] = useAtom(communityBotAtom)
+
+ return (
+
+ );
+};
+
+
diff --git a/src/components/views/rooms/RoomPreviewBar.tsx b/src/components/views/rooms/RoomPreviewBar.tsx
new file mode 100644
index 0000000000..263d92b53b
--- /dev/null
+++ b/src/components/views/rooms/RoomPreviewBar.tsx
@@ -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 {
+ 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 {
+ // 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().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 } {
+ 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): 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 = (
+
+
+ {_t("room|loading_preview")}
+
+ );
+ }
+ 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 = ;
+
+ const inviteMember = this.getInviteMember();
+ let inviterElement: JSX.Element;
+ if (inviteMember) {
+ inviterElement = (
+
+ {inviteMember.rawDisplayName} (
+ {inviteMember.userId})
+
+ );
+ } else {
+ inviterElement = {this.props.inviterName};
+ }
+
+ 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 = (
+
+ );
+ }
+
+ primaryActionHandler = this.props.onJoinClick;
+ secondaryActionLabel = _t("action|reject");
+ secondaryActionHandler = this.props.onRejectClick;
+
+ if (this.props.onRejectAndIgnoreClick) {
+ extraComponents.push(
+
+ {_t("room|invite_reject_ignore")}
+ ,
+ );
+ }
+ 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) => (
+
+ {label}
+
+ ),
+ },
+ ),
+ ];
+ break;
+ }
+ case MessageCase.PromptAskToJoin: {
+ if (roomName) {
+ title = _t("room|knock_prompt_name", { roomName });
+ } else {
+ title = _t("room|knock_prompt");
+ }
+
+ const avatar = ;
+ subTitle = [avatar, _t("room|knock_subtitle")];
+
+ reasonElement = (
+
+ );
+
+ 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 = [
+ <>
+
+ {_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) => {t}
);
+ }
+
+ let titleElement;
+ if (showSpinner) {
+ titleElement = (
+
+
+ {title}
+
+ );
+ } else {
+ titleElement = {title}
;
+ }
+
+ let primaryButton;
+ if (primaryActionHandler) {
+ primaryButton = (
+
+ {primaryActionLabel}
+
+ );
+ }
+
+ let secondaryButton;
+ if (secondaryActionHandler) {
+ secondaryButton = (
+
+ {secondaryActionLabel}
+
+ );
+ }
+
+ if(isCommunityRoom) {
+ secondaryButton = primaryButton;
+ primaryButton = ();
+ titleElement = ();
+ }
+
+ 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 (
+
+
+ {titleElement}
+ {subTitleElements}
+
+ {reasonElement}
+
+ {actions}
+
+
{footer}
+
+ );
+ }
+}
diff --git a/src/hooks/useVerifiedRoom.ts b/src/hooks/useVerifiedRoom.ts
index 9b9cffe7de..3e0e603421 100644
--- a/src/hooks/useVerifiedRoom.ts
+++ b/src/hooks/useVerifiedRoom.ts
@@ -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("$"),
+ }
+}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 27588f25bf..d495c1b6d0 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -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."
}
}