Merge pull request #50 from superhero-com/features/trusted-bots
Features/trusted bots (#47, #43, #32, #31, #16)pull/27073/head
commit
687e651813
|
@ -1,19 +1,20 @@
|
||||||
{
|
{
|
||||||
"default_server_config": {
|
"default_server_config": {
|
||||||
"m.homeserver": {
|
"m.homeserver": {
|
||||||
"base_url": "https://matrix-client.matrix.org",
|
"base_url": "https://matrix.superhero.com",
|
||||||
"server_name": "matrix.org"
|
"server_name": "superhero.com"
|
||||||
},
|
},
|
||||||
"m.identity_server": {
|
"m.identity_server": {
|
||||||
"base_url": "https://vector.im"
|
"base_url": "https://vector.im"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bots_backend_url": "https://http://matrix.superhero.com/wallet",
|
"bots_backend_url": "https://matrix.superhero.com/walletbot",
|
||||||
|
"permalink_prefix": "https://chat.superhero.com",
|
||||||
"disable_custom_urls": false,
|
"disable_custom_urls": false,
|
||||||
"disable_guests": false,
|
"disable_guests": false,
|
||||||
"disable_login_language_selector": false,
|
"disable_login_language_selector": false,
|
||||||
"disable_3pid_login": false,
|
"disable_3pid_login": false,
|
||||||
"brand": "Element",
|
"brand": "Superhero",
|
||||||
"integrations_ui_url": "https://scalar.vector.im/",
|
"integrations_ui_url": "https://scalar.vector.im/",
|
||||||
"integrations_rest_url": "https://scalar.vector.im/api",
|
"integrations_rest_url": "https://scalar.vector.im/api",
|
||||||
"integrations_widgets_urls": [
|
"integrations_widgets_urls": [
|
||||||
|
@ -47,5 +48,6 @@
|
||||||
"brand": "Element Call"
|
"brand": "Element Call"
|
||||||
},
|
},
|
||||||
"map_style_url": "https://api.maptiler.com/maps/streets/style.json?key=fU3vlMsMn4Jb6dnEIFsx",
|
"map_style_url": "https://api.maptiler.com/maps/streets/style.json?key=fU3vlMsMn4Jb6dnEIFsx",
|
||||||
"community_bot_user_id": "@communitybot:superhero.com"
|
"community_bot_user_id": "@communitybot:superhero.com",
|
||||||
|
"wallet_bot_user_id": "@walletbot:superhero.com"
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,7 +105,7 @@ h2 .sh_VerifiedIcon {
|
||||||
|
|
||||||
.mx_AccessibleButton.mx_LegacyRoomHeader_button.mx_AccessibleButton_disabled {
|
.mx_AccessibleButton.mx_LegacyRoomHeader_button.mx_AccessibleButton_disabled {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cpd-theme-dark .mx_SpacePanel .mx_AccessibleButton.mx_SpacePanel_toggleCollapse {
|
.cpd-theme-dark .mx_SpacePanel .mx_AccessibleButton.mx_SpacePanel_toggleCollapse {
|
||||||
background-color: white !important;
|
background-color: white !important;
|
||||||
|
@ -221,7 +221,7 @@ h2 .sh_VerifiedIcon {
|
||||||
--cpd-color-alpha-gray-500: hsla(214, 41%, 97%, 0.15);
|
--cpd-color-alpha-gray-500: hsla(214, 41%, 97%, 0.15);
|
||||||
--cpd-color-pink-1200: #c81fb7 !important;
|
--cpd-color-pink-1200: #c81fb7 !important;
|
||||||
--cpd-color-pink-300: #544352 !important;
|
--cpd-color-pink-300: #544352 !important;
|
||||||
--cpd-color-fuchsia-1200: #D538EE !important;
|
--cpd-color-fuchsia-1200: #d538ee !important;
|
||||||
--cpd-color-fuchsia-300: #52424f !important;
|
--cpd-color-fuchsia-300: #52424f !important;
|
||||||
--cpd-color-purple-1200: #9a30fd !important;
|
--cpd-color-purple-1200: #9a30fd !important;
|
||||||
--cpd-color-purple-300: #443f4c !important;
|
--cpd-color-purple-300: #443f4c !important;
|
||||||
|
@ -269,22 +269,35 @@ h2 .sh_VerifiedIcon {
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_HomePage_title svg {
|
.mx_HomePage_title svg {
|
||||||
heigh: 44px;
|
height: 44px;
|
||||||
width: 173.99px;
|
width: 173.99px;
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_HomePage_default_wrapper .chat_screen_shot {
|
||||||
|
max-width: 70%;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_HomePage_default_buttons_title {
|
.mx_HomePage_default_buttons_title {
|
||||||
font-size: 30px;
|
font-size: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cpd-theme-dark .mx_HomePage_default_buttons_title {
|
.cpd-theme-dark .mx_HomePage_default_buttons_title {
|
||||||
opacity: 0.7;
|
color: rgba(255, 255, 255, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cpd-theme-dark .mx_HomePage_default_buttons_title span {
|
||||||
|
color: rgba(255, 255, 255, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_HomePage_default .mx_HomePage_default_buttons {
|
.mx_HomePage_default .mx_HomePage_default_buttons {
|
||||||
margin: 10px auto 0 !important;
|
margin: 10px auto 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cpd-theme-dark .mx_HomePage_default .mx_HomePage_default_buttons.browsers .mx_HomePage_button_custom {
|
||||||
|
background-color: rgba(255, 255, 255, 0.1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_HomePage_default .mx_HomePage_default_buttons .mx_AccessibleButton.mx_HomePage_button_custom {
|
.mx_HomePage_default .mx_HomePage_default_buttons .mx_AccessibleButton.mx_HomePage_button_custom {
|
||||||
width: auto !important;
|
width: auto !important;
|
||||||
min-height: auto !important;
|
min-height: auto !important;
|
||||||
|
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 3.6 MiB After Width: | Height: | Size: 216 KiB |
|
@ -11,6 +11,7 @@ export type BareUser = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const verifiedAccountsAtom = atomWithStorage<Record<string, string>>("VERIFIED_ACCOUNTS", {});
|
export const verifiedAccountsAtom = atomWithStorage<Record<string, string>>("VERIFIED_ACCOUNTS", {});
|
||||||
|
export const verifiedBotsAtom = atomWithStorage<Record<string, string>>("VERIFIED_BOTS", {});
|
||||||
export const minimumTokenThresholdAtom = atomWithStorage<Record<string, TokenThreshold>>("TOKEN_THRESHOLD", {});
|
export const minimumTokenThresholdAtom = atomWithStorage<Record<string, TokenThreshold>>("TOKEN_THRESHOLD", {});
|
||||||
export const communityBotAtom = atomWithStorage<BareUser>("COMMUNITY_BOT", {
|
export const communityBotAtom = atomWithStorage<BareUser>("COMMUNITY_BOT", {
|
||||||
userId: "",
|
userId: "",
|
||||||
|
|
|
@ -34,7 +34,7 @@ interface IProps {
|
||||||
|
|
||||||
const HomePage: React.FC<IProps> = () => {
|
const HomePage: React.FC<IProps> = () => {
|
||||||
const cli = useMatrixClientContext();
|
const cli = useMatrixClientContext();
|
||||||
const config = SdkConfig.get();
|
const config: any = SdkConfig.get();
|
||||||
const pageUrl = getHomePageUrl(config, cli);
|
const pageUrl = getHomePageUrl(config, cli);
|
||||||
|
|
||||||
if (pageUrl) {
|
if (pageUrl) {
|
||||||
|
@ -44,7 +44,7 @@ const HomePage: React.FC<IProps> = () => {
|
||||||
return (
|
return (
|
||||||
<AutoHideScrollbar className="mx_HomePage mx_HomePage_default" element="main">
|
<AutoHideScrollbar className="mx_HomePage mx_HomePage_default" element="main">
|
||||||
<div className="mx_HomePage_default_wrapper">
|
<div className="mx_HomePage_default_wrapper">
|
||||||
<ChatScreenShot />
|
<ChatScreenShot className="chat_screen_shot" />
|
||||||
<div className="mx_HomePage_title">
|
<div className="mx_HomePage_title">
|
||||||
<SuperheroLogo />
|
<SuperheroLogo />
|
||||||
<div>is so much better with our Wallet</div>
|
<div>is so much better with our Wallet</div>
|
||||||
|
@ -52,7 +52,7 @@ const HomePage: React.FC<IProps> = () => {
|
||||||
<div className="mx_HomePage_default_buttons_title">
|
<div className="mx_HomePage_default_buttons_title">
|
||||||
<span style={{ fontWeight: "bold" }}>1. </span>Download extension for your browser
|
<span style={{ fontWeight: "bold" }}>1. </span>Download extension for your browser
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_HomePage_default_buttons">
|
<div className="mx_HomePage_default_buttons browsers">
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
onClick={(): void => {
|
onClick={(): void => {
|
||||||
window.open("https://addons.mozilla.org/en-US/firefox/addon/superhero-wallet/", "_blank");
|
window.open("https://addons.mozilla.org/en-US/firefox/addon/superhero-wallet/", "_blank");
|
||||||
|
@ -81,7 +81,7 @@ const HomePage: React.FC<IProps> = () => {
|
||||||
<div className="mx_HomePage_default_buttons">
|
<div className="mx_HomePage_default_buttons">
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
onClick={(): void => {
|
onClick={(): void => {
|
||||||
startDmOnFirstMessage(cli, [new DirectoryMember({ user_id: "@walletbot:superhero.com" })]);
|
startDmOnFirstMessage(cli, [new DirectoryMember({ user_id: config.wallet_bot_user_id })]);
|
||||||
}}
|
}}
|
||||||
className="mx_HomePage_button_custom"
|
className="mx_HomePage_button_custom"
|
||||||
>
|
>
|
||||||
|
|
|
@ -81,6 +81,7 @@ import { SdkContextClass } from "matrix-react-sdk/src/contexts/SDKContext";
|
||||||
import { UserProfilesStore } from "matrix-react-sdk/src/stores/UserProfilesStore";
|
import { UserProfilesStore } from "matrix-react-sdk/src/stores/UserProfilesStore";
|
||||||
|
|
||||||
import { UserVerifiedBadge } from "../elements/UserVerifiedBadge";
|
import { UserVerifiedBadge } from "../elements/UserVerifiedBadge";
|
||||||
|
import { BotVerifiedBadge } from "../elements/BotVerifiedBadge";
|
||||||
|
|
||||||
// we have a number of types defined from the Matrix spec which can't reasonably be altered here.
|
// we have a number of types defined from the Matrix spec which can't reasonably be altered here.
|
||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
|
@ -288,6 +289,7 @@ class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
|
||||||
<div className="mx_InviteDialog_tile_nameStack_name">
|
<div className="mx_InviteDialog_tile_nameStack_name">
|
||||||
{this.highlightName(this.props.member.name)}
|
{this.highlightName(this.props.member.name)}
|
||||||
<UserVerifiedBadge userId={this.props.member.userId} />
|
<UserVerifiedBadge userId={this.props.member.userId} />
|
||||||
|
<BotVerifiedBadge userId={this.props.member.userId} />
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_InviteDialog_tile_nameStack_userId">{caption}</div>
|
<div className="mx_InviteDialog_tile_nameStack_userId">{caption}</div>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { useVerifiedBot } from "../../../hooks/useVerifiedBot";
|
||||||
|
|
||||||
|
export interface UserVerifiedBadgeProps {
|
||||||
|
userId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BotVerifiedBadge = ({ userId }: UserVerifiedBadgeProps): JSX.Element => {
|
||||||
|
const isVerifiedBot = useVerifiedBot(userId);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{isVerifiedBot && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
color: "rgba(30, 203, 172, 1)",
|
||||||
|
fontWeight: 700,
|
||||||
|
fontSize: "10px",
|
||||||
|
backgroundColor: "rgba(30, 203, 172, 0.2)",
|
||||||
|
padding: "2px 4px",
|
||||||
|
borderRadius: "15px",
|
||||||
|
marginLeft: "15px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Trusted Bot
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
|
@ -156,12 +156,12 @@ export const Pill: React.FC<PillProps> = ({ type: propType, url, inMessage, room
|
||||||
<>
|
<>
|
||||||
{isCommunityRoom ? (
|
{isCommunityRoom ? (
|
||||||
<>
|
<>
|
||||||
<CommunityRoomIcon className="sh_RoomTokenGatedRoomIcon" style={{ marginLeft: "5px" }} />
|
<CommunityRoomIcon className="sh_RoomTokenGatedRoomIcon" style={{ marginLeft: "2px" }} />
|
||||||
<span>$</span>
|
<span>$</span>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
{isTokenGatedRoom ? (
|
{isTokenGatedRoom ? (
|
||||||
<TokenGatedRoomIcon className="sh_RoomTokenGatedRoomIcon" style={{ marginLeft: "5px" }} />
|
<TokenGatedRoomIcon className="sh_RoomTokenGatedRoomIcon" style={{ marginLeft: "2px" }} />
|
||||||
) : null}
|
) : null}
|
||||||
<span className="mx_Pill_text">{getSafeRoomName(pillText || "")}</span>
|
<span className="mx_Pill_text">{getSafeRoomName(pillText || "")}</span>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -22,6 +22,7 @@ import { Icon as CommunityRoomIcon } from "../../../../res/themes/superhero/img/
|
||||||
import { useRoomName } from "../../../hooks/useRoomName";
|
import { useRoomName } from "../../../hooks/useRoomName";
|
||||||
import { useVerifiedRoom } from "../../../hooks/useVerifiedRoom";
|
import { useVerifiedRoom } from "../../../hooks/useVerifiedRoom";
|
||||||
import { UserVerifiedBadge } from "./UserVerifiedBadge";
|
import { UserVerifiedBadge } from "./UserVerifiedBadge";
|
||||||
|
import { BotVerifiedBadge } from "./BotVerifiedBadge";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
room?: Room | IPublicRoomsChunkRoom;
|
room?: Room | IPublicRoomsChunkRoom;
|
||||||
|
@ -33,16 +34,18 @@ export const RoomName = ({ room, children, maxLength }: IProps): JSX.Element =>
|
||||||
const roomName = useRoomName(room);
|
const roomName = useRoomName(room);
|
||||||
const { isTokenGatedRoom, isCommunityRoom } = useVerifiedRoom(room);
|
const { isTokenGatedRoom, isCommunityRoom } = useVerifiedRoom(room);
|
||||||
|
|
||||||
const roomUsers: string[] = useMemo(() => {
|
const roomUser: string | undefined = useMemo(() => {
|
||||||
if ((room as Room).getJoinedMemberCount?.() > 2 || (room as IPublicRoomsChunkRoom).num_joined_members > 2) {
|
// check if this is a DM room and if so, return the other user's ID
|
||||||
return [];
|
const dmUserId = (room as Room)?.guessDMUserId?.();
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
// need to access the private summaryHeroes property, to know if it's a DM room
|
||||||
|
if (!(room as Room)?.summaryHeroes) {
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
return (
|
|
||||||
(room as Room)
|
return dmUserId && dmUserId !== (room as Room).myUserId ? dmUserId : undefined;
|
||||||
?.getMembers?.()
|
|
||||||
.map((m: { userId: string }) => m.userId)
|
|
||||||
.filter((userId: string) => !!userId && userId !== (room as Room)?.myUserId) || []
|
|
||||||
);
|
|
||||||
}, [room]);
|
}, [room]);
|
||||||
|
|
||||||
const truncatedRoomName = useMemo(() => {
|
const truncatedRoomName = useMemo(() => {
|
||||||
|
@ -63,12 +66,11 @@ export const RoomName = ({ room, children, maxLength }: IProps): JSX.Element =>
|
||||||
)}
|
)}
|
||||||
{isTokenGatedRoom && <TokenGatedRoomIcon className="sh_RoomTokenGatedRoomIcon" />}
|
{isTokenGatedRoom && <TokenGatedRoomIcon className="sh_RoomTokenGatedRoomIcon" />}
|
||||||
<span dir="auto">{truncatedRoomName}</span>
|
<span dir="auto">{truncatedRoomName}</span>
|
||||||
{roomUsers?.length && !isTokenGatedRoom && !isCommunityRoom ? (
|
{roomUser && !isTokenGatedRoom && !isCommunityRoom ? <UserVerifiedBadge userId={roomUser} /> : null}
|
||||||
<UserVerifiedBadge userId={roomUsers[0]} />
|
{roomUser && !isTokenGatedRoom && !isCommunityRoom ? <BotVerifiedBadge userId={roomUser} /> : null}
|
||||||
) : null}
|
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
[truncatedRoomName, isCommunityRoom, isTokenGatedRoom, roomUsers],
|
[truncatedRoomName, isCommunityRoom, isTokenGatedRoom, roomUser],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (children) return children(renderRoomName());
|
if (children) return children(renderRoomName());
|
||||||
|
|
|
@ -84,6 +84,7 @@ import UIStore from "matrix-react-sdk/src/stores/UIStore";
|
||||||
|
|
||||||
import { UserVerifiedBadge } from "../elements/UserVerifiedBadge";
|
import { UserVerifiedBadge } from "../elements/UserVerifiedBadge";
|
||||||
import { MessageButton } from "../elements/MessageButton";
|
import { MessageButton } from "../elements/MessageButton";
|
||||||
|
import { BotVerifiedBadge } from "../elements/BotVerifiedBadge";
|
||||||
|
|
||||||
export interface IDevice extends Device {
|
export interface IDevice extends Device {
|
||||||
ambiguous?: boolean;
|
ambiguous?: boolean;
|
||||||
|
@ -1617,6 +1618,7 @@ export const UserInfoHeader: React.FC<{
|
||||||
<div>
|
<div>
|
||||||
<h2>
|
<h2>
|
||||||
<UserVerifiedBadge userId={member.userId} />
|
<UserVerifiedBadge userId={member.userId} />
|
||||||
|
<BotVerifiedBadge userId={member.userId} />
|
||||||
<span title={displayName} aria-label={displayName} dir="auto">
|
<span title={displayName} aria-label={displayName} dir="auto">
|
||||||
{displayName}
|
{displayName}
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import React, { useCallback, useEffect } from "react";
|
import React, { useCallback, useEffect } from "react";
|
||||||
|
|
||||||
import { communityBotAtom, minimumTokenThresholdAtom, verifiedAccountsAtom } from "../atoms";
|
import { communityBotAtom, minimumTokenThresholdAtom, verifiedAccountsAtom, verifiedBotsAtom } from "../atoms";
|
||||||
|
|
||||||
const useMinimumTokenThreshold = (config: any): void => {
|
const useMinimumTokenThreshold = (config: any): void => {
|
||||||
const [, setMinimumTokenThreshold] = useAtom(minimumTokenThresholdAtom);
|
const [, setMinimumTokenThreshold] = useAtom(minimumTokenThresholdAtom);
|
||||||
|
@ -43,6 +43,7 @@ const useMinimumTokenThreshold = (config: any): void => {
|
||||||
*/
|
*/
|
||||||
export const SuperheroProvider = ({ children, config }: any): any => {
|
export const SuperheroProvider = ({ children, config }: any): any => {
|
||||||
const [verifiedAccounts, setVerifiedAccounts] = useAtom(verifiedAccountsAtom);
|
const [verifiedAccounts, setVerifiedAccounts] = useAtom(verifiedAccountsAtom);
|
||||||
|
const [, setVerifiedBots] = useAtom(verifiedBotsAtom);
|
||||||
const [, setCommunityBot] = useAtom(communityBotAtom);
|
const [, setCommunityBot] = useAtom(communityBotAtom);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -65,7 +66,15 @@ export const SuperheroProvider = ({ children, config }: any): any => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function loadVerifiedBots(): void {
|
||||||
|
setVerifiedBots({
|
||||||
|
[config.community_bot_user_id]: "true",
|
||||||
|
[config.wallet_bot_user_id]: "true",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
loadVerifiedBots();
|
||||||
if (!verifiedAccounts?.length) {
|
if (!verifiedAccounts?.length) {
|
||||||
loadVerifiedAccounts();
|
loadVerifiedAccounts();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import { useAtom } from "jotai";
|
||||||
|
|
||||||
|
import { verifiedBotsAtom } from "../atoms";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom hook to check if a bot is verified.
|
||||||
|
* @param botId - The ID of the bot to check.
|
||||||
|
* @returns A boolean indicating whether the bot is verified or not.
|
||||||
|
*/
|
||||||
|
export function useVerifiedBot(botId?: string): boolean {
|
||||||
|
const [verifiedBots] = useAtom(verifiedBotsAtom);
|
||||||
|
|
||||||
|
const isVerifiedBot: boolean = useMemo(() => {
|
||||||
|
return !!(botId && !!verifiedBots[botId]);
|
||||||
|
}, [botId, verifiedBots]);
|
||||||
|
|
||||||
|
return isVerifiedBot;
|
||||||
|
}
|
Loading…
Reference in New Issue