Skip to content

Commit

Permalink
feat: verified user badge
Browse files Browse the repository at this point in the history
  • Loading branch information
ifaouibadi committed Dec 6, 2023
1 parent 44a41b3 commit 94cc724
Show file tree
Hide file tree
Showing 9 changed files with 130 additions and 26 deletions.
1 change: 1 addition & 0 deletions config.sample.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"base_url": "https://vector.im"
}
},
"bots_backend_url": "https://http://matrix.superhero.com/wallet",
"disable_custom_urls": false,
"disable_guests": false,
"disable_login_language_selector": false,
Expand Down
11 changes: 9 additions & 2 deletions res/css/superhero/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,24 @@
display: flex;
}

.sh_RoomTokenGatedRoomIcon {
.sh_RoomTokenGatedRoomIcon,
.sh_VerifiedIcon {
width: 16px;
height: 16px;
margin-right: 4px;
}

h2 .sh_RoomTokenGatedRoomIcon {
h2 .sh_RoomTokenGatedRoomIcon,
h2 .sh_VerifiedIcon {
width: 26px;
height: 26px;
}

.sh_VerifiedIcon {
margin-right: 4px;
margin-left: 4px;
}

.mx_QuickSettingsButton.sh_SuperheroDexButton::before {
-webkit-mask-image: url(../../themes/superhero/img/icons/diamond.svg);
mask-image: url(../../themes/superhero/img/icons/diamond.svg);
Expand Down
13 changes: 13 additions & 0 deletions res/themes/superhero/img/icons/verified.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 14 additions & 4 deletions src/components/views/elements/RoomName.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ import { IPublicRoomsChunkRoom, Room } from "matrix-js-sdk/src/matrix";
import React, { useCallback, useMemo } from "react";

import { Icon as TokenGatedRoomIcon } from "../../../../res/themes/superhero/img/icons/tokengated-room.svg";
import { isTokenGatedRoom, useRoomName } from "../../../hooks/useRoomName";
import { Icon as VerifiedIcon } from "../../../../res/themes/superhero/img/icons/verified.svg";
import { useRoomName } from "../../../hooks/useRoomName";
import { useVerifiedRoom } from "../../../hooks/useVerifiedRoom";
import { useVerifiedUser } from "../../../hooks/useVerifiedUser";

interface IProps {
room?: Room | IPublicRoomsChunkRoom;
Expand All @@ -29,10 +32,16 @@ interface IProps {
export const RoomName = ({ room, children, maxLength }: IProps): JSX.Element => {
const roomName = useRoomName(room);

const isVerifiedRoom = useMemo(() => {
return isTokenGatedRoom(room);
const roomUsers: string[] = useMemo(() => {
return (room as Room)
.getMembers()
.map((m) => m.userId)
.filter((userId) => !!userId && userId != (room as Room).myUserId);
}, [room]);

const isVerifiedUser = useVerifiedUser(roomUsers?.length > 0 ? roomUsers[0] : undefined);
const isVerifiedRoom = useVerifiedRoom(room);

const truncatedRoomName = useMemo(() => {
if (maxLength && roomName.length > maxLength) {
return `${roomName.substring(0, maxLength)}...`;
Expand All @@ -45,9 +54,10 @@ export const RoomName = ({ room, children, maxLength }: IProps): JSX.Element =>
<span className="sh_RoomTokenGatedRoom">
{isVerifiedRoom && <TokenGatedRoomIcon className="sh_RoomTokenGatedRoomIcon" />}
<span dir="auto">{truncatedRoomName}</span>
{isVerifiedUser && <VerifiedIcon className="sh_VerifiedIcon" />}
</span>
),
[truncatedRoomName, isVerifiedRoom],
[truncatedRoomName, isVerifiedRoom, isVerifiedUser],
);

if (children) return children(renderRoomName());
Expand Down
37 changes: 37 additions & 0 deletions src/context/SuperheroProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React, { createContext, useEffect, useState } from "react";

export const SuperheroContext = createContext<{
verifiedAccounts: Record<string, string>;
}>({
verifiedAccounts: {},
});

/**
* Provides the superhero context to its children components.
* @param children The child components to be wrapped by the provider.
* @returns The superhero provider component.
*/
export const SuperheroProvider = ({ children, config }: any): any => {
const [verifiedAccounts, setVerifiedAccounts] = useState<Record<string, string>>({});

function loadVerifiedAccounts(): void {
fetch(`${config.bots_backend_url}/ae-wallet-bot/get-verified-accounts`, {
method: "POST",
})
.then((res) => res.json())
.then(setVerifiedAccounts);
}

useEffect(() => {
loadVerifiedAccounts();

const interval = setInterval(() => {
loadVerifiedAccounts();
}, 10000);

return () => clearInterval(interval);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

return <SuperheroContext.Provider value={{ verifiedAccounts }}>{children}</SuperheroContext.Provider>;
};
9 changes: 0 additions & 9 deletions src/hooks/useRoomName.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,6 @@ export function getRoomName(room?: Room | IPublicRoomsChunkRoom, oobName?: IOOBD
);
}

/**
* Determines if a room is a token gated room
* @param room - The room model
* @returns {boolean} true if the room is token gated
*/
export function isTokenGatedRoom(room?: Room | IPublicRoomsChunkRoom): boolean {
return !!room?.name?.includes("[TG]");
}

/**
* Determines the room name from a combination of the room model and potential
* out-of-band information
Expand Down
24 changes: 24 additions & 0 deletions src/hooks/useVerifiedRoom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { IPublicRoomsChunkRoom, Room } from "matrix-js-sdk/src/matrix";
import { useMemo } from "react";

/**
* Determines if a room is a token gated room
* @param room - The room model
* @returns {boolean} true if the room is token gated
*/
export function isTokenGatedRoom(room?: Room | IPublicRoomsChunkRoom): boolean {
return !!room?.name?.startsWith("[TG]");
}

/**
* Custom hook to check if a room is verified
* @param room - The room model
* @returns {boolean} true if the room is verified, false otherwise
*/
export function useVerifiedRoom(room?: Room | IPublicRoomsChunkRoom): boolean {
const isVerifiedRoom = useMemo(() => {
return isTokenGatedRoom(room);
}, [room]);

return isVerifiedRoom;
}
18 changes: 18 additions & 0 deletions src/hooks/useVerifiedUser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useContext, useMemo } from "react";

import { SuperheroContext } from "../context/SuperheroProvider";

/**
* Custom hook to check if a user is verified.
* @param userId - The ID of the user to check.
* @returns A boolean indicating whether the user is verified or not.
*/
export function useVerifiedUser(userId?: string): boolean {
const { verifiedAccounts } = useContext(SuperheroContext);

const isVerifiedUser: boolean = useMemo(() => {
return !!(userId && !!verifiedAccounts[userId]);
}, [userId, verifiedAccounts]);

return isVerifiedUser;
}
25 changes: 14 additions & 11 deletions src/vector/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { parseQs } from "./url_utils";
import VectorBasePlatform from "./platform/VectorBasePlatform";
import { getInitialScreenAfterLogin, getScreenFromLocation, init as initRouting, onNewScreen } from "./routing";
import { UserFriendlyError } from "../languageHandler";
import { SuperheroProvider } from "../context/SuperheroProvider";

// add React and ReactPerf to the global namespace, to make them easier to access via the console
// this incidentally means we can forget our React imports in JSX files without penalty.
Expand Down Expand Up @@ -116,17 +117,19 @@ export async function loadApp(fragParams: {}, matrixChatRef: React.Ref<MatrixCha

return (
<wrapperOpts.Wrapper>
<MatrixChat
ref={matrixChatRef}
onNewScreen={onNewScreen}
config={config}
realQueryParams={params}
startingFragmentQueryParams={fragParams}
enableGuest={!config.disable_guests}
onTokenLoginCompleted={onTokenLoginCompleted}
initialScreenAfterLogin={initialScreenAfterLogin}
defaultDeviceDisplayName={defaultDeviceName}
/>
<SuperheroProvider config={config}>
<MatrixChat
ref={matrixChatRef}
onNewScreen={onNewScreen}
config={config}
realQueryParams={params}
startingFragmentQueryParams={fragParams}
enableGuest={!config.disable_guests}
onTokenLoginCompleted={onTokenLoginCompleted}
initialScreenAfterLogin={initialScreenAfterLogin}
defaultDeviceDisplayName={defaultDeviceName}
/>
</SuperheroProvider>
</wrapperOpts.Wrapper>
);
}
Expand Down

0 comments on commit 94cc724

Please sign in to comment.