diff --git a/src/ConferenceCallManagerHooks.jsx b/src/ConferenceCallManagerHooks.jsx index d506780..b559a25 100644 --- a/src/ConferenceCallManagerHooks.jsx +++ b/src/ConferenceCallManagerHooks.jsx @@ -612,17 +612,35 @@ export function getRoomUrl(roomId) { } } -export function useDisplayName(client) { - const [{ loading, displayName, error, success }, setState] = useState(() => ({ - success: false, - loading: false, - displayName: client?.getUser(client.getUserId())?.displayName, - error: null, - })); +function getAvatarUrl(client, mxcUrl, avatarSize = 96) { + const width = Math.floor(avatarSize * window.devicePixelRatio); + const height = Math.floor(avatarSize * window.devicePixelRatio); + return mxcUrl && client.mxcUrlToHttp(mxcUrl, width, height, "crop"); +} + +export function useProfile(client) { + const [{ loading, displayName, avatarUrl, error, success }, setState] = + useState(() => { + const user = client?.getUser(client.getUserId()); + + return { + success: false, + loading: false, + displayName: user?.displayName, + avatarUrl: user && client && getAvatarUrl(client, user.avatarUrl), + error: null, + }; + }); useEffect(() => { - const onChangeDisplayName = (_event, { displayName }) => { - setState({ success: false, loading: false, displayName, error: null }); + const onChangeUser = (_event, { displayName, avatarUrl }) => { + setState({ + success: false, + loading: false, + displayName, + avatarUrl: getAvatarUrl(client, avatarUrl), + error: null, + }); }; let user; @@ -630,18 +648,20 @@ export function useDisplayName(client) { if (client) { const userId = client.getUserId(); user = client.getUser(userId); - user.on("User.displayName", onChangeDisplayName); + user.on("User.displayName", onChangeUser); + user.on("User.avatarUrl", onChangeUser); } return () => { if (user) { - user.removeListener("User.displayName", onChangeDisplayName); + user.removeListener("User.displayName", onChangeUser); + user.removeListener("User.avatarUrl", onChangeUser); } }; }, [client]); - const setDisplayName = useCallback( - (displayName) => { + const saveProfile = useCallback( + async ({ displayName, avatar }) => { if (client) { setState((prev) => ({ ...prev, @@ -650,30 +670,33 @@ export function useDisplayName(client) { success: false, })); - client - .setDisplayName(displayName) - .then(() => { - setState((prev) => ({ - ...prev, - displayName, - loading: false, - success: true, - })); - }) - .catch((error) => { - setState((prev) => ({ - ...prev, - loading: false, - error, - success: false, - })); - }); + try { + await client.setDisplayName(displayName); + + const url = await client.uploadContent(avatar); + await client.setAvatarUrl(url); + + setState((prev) => ({ + ...prev, + displayName, + avatarUrl: getAvatarUrl(client, url), + loading: false, + success: true, + })); + } catch (error) { + setState((prev) => ({ + ...prev, + loading: false, + error, + success: false, + })); + } } else { - console.error("Client not initialized before calling setDisplayName"); + console.error("Client not initialized before calling saveProfile"); } }, [client] ); - return { loading, error, displayName, setDisplayName, success }; + return { loading, error, displayName, avatarUrl, saveProfile, success }; } diff --git a/src/ProfileModal.jsx b/src/ProfileModal.jsx index 1517672..3e86e86 100644 --- a/src/ProfileModal.jsx +++ b/src/ProfileModal.jsx @@ -1,6 +1,6 @@ import React, { useCallback, useEffect, useState } from "react"; import { Button } from "./button"; -import { useDisplayName } from "./ConferenceCallManagerHooks"; +import { useProfile } from "./ConferenceCallManagerHooks"; import { FieldRow, InputField, ErrorMessage } from "./Input"; import { Modal, ModalContent } from "./Modal"; @@ -11,8 +11,8 @@ export function ProfileModal({ client, ...rest }) { error, loading, displayName: initialDisplayName, - setDisplayName: submitDisplayName, - } = useDisplayName(client); + saveProfile, + } = useProfile(client); const [displayName, setDisplayName] = useState(initialDisplayName || ""); const onChangeDisplayName = useCallback( @@ -27,10 +27,14 @@ export function ProfileModal({ client, ...rest }) { e.preventDefault(); const data = new FormData(e.target); const displayName = data.get("displayName"); - console.log(displayName); - submitDisplayName(displayName); + const avatar = data.get("avatar"); + + saveProfile({ + displayName, + avatar, + }); }, - [setDisplayName] + [saveProfile] ); useEffect(() => { @@ -56,6 +60,9 @@ export function ProfileModal({ client, ...rest }) { onChange={onChangeDisplayName} /> + + + {error && ( {error.message} diff --git a/src/UserMenu.jsx b/src/UserMenu.jsx index 9f99bd0..f67742a 100644 --- a/src/UserMenu.jsx +++ b/src/UserMenu.jsx @@ -8,10 +8,11 @@ import styles from "./UserMenu.module.css"; import { Item } from "@react-stately/collections"; import { Menu } from "./Menu"; import { useHistory, useLocation } from "react-router-dom"; -import { useClient, useDisplayName } from "./ConferenceCallManagerHooks"; +import { useClient, useProfile } from "./ConferenceCallManagerHooks"; import { useModalTriggerState } from "./Modal"; import { ProfileModal } from "./ProfileModal"; import { Tooltip, TooltipTrigger } from "./Tooltip"; +import { Avatar } from "./Avatar"; export function UserMenu({ disableLogout }) { const location = useLocation(); @@ -24,7 +25,7 @@ export function UserMenu({ disableLogout }) { userName, client, } = useClient(); - const { displayName } = useDisplayName(client); + const { displayName, avatarUrl } = useProfile(client); const { modalState, modalProps } = useModalTriggerState(); const onAction = useCallback( @@ -87,7 +88,16 @@ export function UserMenu({ disableLogout }) { {(props) => (