diff --git a/src/ConferenceCallManagerHooks.jsx b/src/ConferenceCallManagerHooks.jsx index d506780f..b559a25e 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/Input.jsx b/src/Input.jsx index 3acc9e87..d890ab3f 100644 --- a/src/Input.jsx +++ b/src/Input.jsx @@ -22,14 +22,16 @@ export function Field({ children, className, ...rest }) { } export const InputField = forwardRef( - ({ id, label, className, type, checked, ...rest }, ref) => { + ({ id, label, className, type, checked, prefix, suffix, ...rest }, ref) => { return ( + {prefix && {prefix}} + {suffix && {suffix}} ); } diff --git a/src/Input.module.css b/src/Input.module.css index 736a3b7b..b47a20db 100644 --- a/src/Input.module.css +++ b/src/Input.module.css @@ -40,6 +40,10 @@ min-width: 0; } +.inputField span { + padding: 11px 9px; +} + .inputField input::placeholder { transition: color 0.25s ease-in 0s; color: transparent; @@ -77,7 +81,8 @@ } .inputField input:focus + label, -.inputField input:not(:placeholder-shown) + label { +.inputField input:not(:placeholder-shown) + label, +.inputField.prefix input + label { background-color: var(--bgColor2); transition: font-size 0.25s ease-out 0s, color 0.25s ease-out 0s, top 0.25s ease-out 0s, background-color 0.25s ease-out 0s; diff --git a/src/LoginPage.jsx b/src/LoginPage.jsx index 67493cd6..c20e7da5 100644 --- a/src/LoginPage.jsx +++ b/src/LoginPage.jsx @@ -67,17 +67,6 @@ export function LoginPage() {

Log In

To continue to Element

- - setHomeServer(e.target.value)} - placeholder="Homeserver" - label="Homeserver" - autoCorrect="off" - autoCapitalize="none" - /> - diff --git a/src/Menu.module.css b/src/Menu.module.css index 294bccda..bb75cecb 100644 --- a/src/Menu.module.css +++ b/src/Menu.module.css @@ -30,10 +30,12 @@ .menuItem.focused:first-child, .menuItem:hover:first-child { - border-radius: 8px 8px 0 0; + border-top-left-radius: 8px; + border-top-right-radius: 8px; } .menuItem.focused:last-child, .menuItem:hover:last-child { - border-radius: 0 0 8px 8px; + border-bottom-left-radius: 8px; + border-bottom-right-radius: 8px; } diff --git a/src/ProfileModal.jsx b/src/ProfileModal.jsx index 15176725..3e86e862 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/RegisterPage.jsx b/src/RegisterPage.jsx index 4209bc5e..a5cf3bb4 100644 --- a/src/RegisterPage.jsx +++ b/src/RegisterPage.jsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { useCallback, useRef, useState } from "react"; +import React, { useCallback, useEffect, useRef, useState } from "react"; import { useHistory, useLocation, Link } from "react-router-dom"; import { FieldRow, InputField, ErrorMessage } from "./Input"; import { Button } from "./button"; @@ -23,14 +23,15 @@ import styles from "./LoginPage.module.css"; import { ReactComponent as Logo } from "./icons/LogoLarge.svg"; export function RegisterPage() { - // TODO: Handle hitting login page with authenticated client const { register } = useClient(); const registerUsernameRef = useRef(); - const registerPasswordRef = useRef(); + const confirmPasswordRef = useRef(); const history = useHistory(); const location = useLocation(); const [loading, setLoading] = useState(false); const [error, setError] = useState(); + const [password, setPassword] = useState(""); + const [passwordConfirmation, setPasswordConfirmation] = useState(""); const onSubmitRegisterForm = useCallback( (e) => { @@ -55,6 +56,14 @@ export function RegisterPage() { [register, location, history] ); + useEffect(() => { + if (password && passwordConfirmation && password !== passwordConfirmation) { + confirmPasswordRef.current.setCustomValidity("Passwords must match"); + } else { + confirmPasswordRef.current.setCustomValidity(""); + } + }, [password, passwordConfirmation]); + return ( <>
@@ -71,16 +80,31 @@ export function RegisterPage() { label="Username" autoCorrect="off" autoCapitalize="none" + prefix="@" + suffix={`:${window.location.host}`} /> setPassword(e.target.value)} + value={password} placeholder="Password" label="Password" /> + + setPasswordConfirmation(e.target.value)} + value={passwordConfirmation} + placeholder="Confirm Password" + label="Confirm Password" + ref={confirmPasswordRef} + /> + {error && ( {error.message} diff --git a/src/Room.jsx b/src/Room.jsx index 07c04962..0d2ed568 100644 --- a/src/Room.jsx +++ b/src/Room.jsx @@ -301,8 +301,8 @@ function RoomSetupView({
-

{roomName}

+