Merge pull request #50 from superhero-com/features/trusted-bots

Features/trusted bots (#47, #43, #32, #31, #16)
pull/27073/head
Badi Ifaoui 2024-01-26 09:58:07 +01:00 committed by GitHub
commit 687e651813
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 128 additions and 64 deletions

View File

@ -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"
} }

View File

@ -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

View File

@ -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: "",

View File

@ -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"
> >

View File

@ -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>

View File

@ -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>
)}
</>
);
};

View File

@ -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>
</> </>

View File

@ -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());

View File

@ -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>

View File

@ -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();
} }

View File

@ -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;
}