diff --git a/src/ConferenceCallManagerHooks.jsx b/src/ConferenceCallManagerHooks.jsx
index 5e841e7d..d506780f 100644
--- a/src/ConferenceCallManagerHooks.jsx
+++ b/src/ConferenceCallManagerHooks.jsx
@@ -104,7 +104,15 @@ export async function fetchGroupCall(
export function ClientProvider({ homeserverUrl, children }) {
const history = useHistory();
const [
- { loading, isAuthenticated, isPasswordlessUser, isGuest, client, userName },
+ {
+ loading,
+ isAuthenticated,
+ isPasswordlessUser,
+ isGuest,
+ client,
+ userName,
+ displayName,
+ },
setState,
] = useState({
loading: true,
@@ -113,6 +121,7 @@ export function ClientProvider({ homeserverUrl, children }) {
isGuest: false,
client: undefined,
userName: null,
+ displayName: null,
});
useEffect(() => {
@@ -602,3 +611,69 @@ export function getRoomUrl(roomId) {
return `${window.location.host}/room/${roomId}`;
}
}
+
+export function useDisplayName(client) {
+ const [{ loading, displayName, error, success }, setState] = useState(() => ({
+ success: false,
+ loading: false,
+ displayName: client?.getUser(client.getUserId())?.displayName,
+ error: null,
+ }));
+
+ useEffect(() => {
+ const onChangeDisplayName = (_event, { displayName }) => {
+ setState({ success: false, loading: false, displayName, error: null });
+ };
+
+ let user;
+
+ if (client) {
+ const userId = client.getUserId();
+ user = client.getUser(userId);
+ user.on("User.displayName", onChangeDisplayName);
+ }
+
+ return () => {
+ if (user) {
+ user.removeListener("User.displayName", onChangeDisplayName);
+ }
+ };
+ }, [client]);
+
+ const setDisplayName = useCallback(
+ (displayName) => {
+ if (client) {
+ setState((prev) => ({
+ ...prev,
+ loading: true,
+ error: null,
+ success: false,
+ }));
+
+ client
+ .setDisplayName(displayName)
+ .then(() => {
+ setState((prev) => ({
+ ...prev,
+ displayName,
+ loading: false,
+ success: true,
+ }));
+ })
+ .catch((error) => {
+ setState((prev) => ({
+ ...prev,
+ loading: false,
+ error,
+ success: false,
+ }));
+ });
+ } else {
+ console.error("Client not initialized before calling setDisplayName");
+ }
+ },
+ [client]
+ );
+
+ return { loading, error, displayName, setDisplayName, success };
+}
diff --git a/src/Input.module.css b/src/Input.module.css
index 234cf275..1951ebc9 100644
--- a/src/Input.module.css
+++ b/src/Input.module.css
@@ -15,15 +15,11 @@
justify-content: flex-end;
}
-.fieldRow > .field {
- margin: 0 5px;
+.fieldRow > * {
+ margin-right: 24px;
}
-.fieldRow > .field:first-child {
- margin-left: 0;
-}
-
-.fieldRow > .field:last-child {
+.fieldRow > :last-child {
margin-right: 0;
}
diff --git a/src/Modal.module.css b/src/Modal.module.css
index c9270eb8..f83db900 100644
--- a/src/Modal.module.css
+++ b/src/Modal.module.css
@@ -16,7 +16,7 @@
box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.15);
border-radius: 8px;
max-width: 90vw;
- min-width: 288px;
+ width: 600px;
}
.modalHeader {
diff --git a/src/ProfileModal.jsx b/src/ProfileModal.jsx
new file mode 100644
index 00000000..15176725
--- /dev/null
+++ b/src/ProfileModal.jsx
@@ -0,0 +1,76 @@
+import React, { useCallback, useEffect, useState } from "react";
+import { Button } from "./button";
+import { useDisplayName } from "./ConferenceCallManagerHooks";
+import { FieldRow, InputField, ErrorMessage } from "./Input";
+import { Modal, ModalContent } from "./Modal";
+
+export function ProfileModal({ client, ...rest }) {
+ const { onClose } = rest;
+ const {
+ success,
+ error,
+ loading,
+ displayName: initialDisplayName,
+ setDisplayName: submitDisplayName,
+ } = useDisplayName(client);
+ const [displayName, setDisplayName] = useState(initialDisplayName || "");
+
+ const onChangeDisplayName = useCallback(
+ (e) => {
+ setDisplayName(e.target.value);
+ },
+ [setDisplayName]
+ );
+
+ const onSubmit = useCallback(
+ (e) => {
+ e.preventDefault();
+ const data = new FormData(e.target);
+ const displayName = data.get("displayName");
+ console.log(displayName);
+ submitDisplayName(displayName);
+ },
+ [setDisplayName]
+ );
+
+ useEffect(() => {
+ if (success) {
+ onClose();
+ }
+ }, [success, onClose]);
+
+ return (
+
+
+
+
+
+ );
+}
diff --git a/src/ProfileModal.module.css b/src/ProfileModal.module.css
new file mode 100644
index 00000000..e69de29b
diff --git a/src/UserMenu.jsx b/src/UserMenu.jsx
index 8fd6f03d..b7d15883 100644
--- a/src/UserMenu.jsx
+++ b/src/UserMenu.jsx
@@ -8,17 +8,22 @@ 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 } from "./ConferenceCallManagerHooks";
+import { useClient, useDisplayName } from "./ConferenceCallManagerHooks";
+import { useModalTriggerState } from "./Modal";
+import { ProfileModal } from "./ProfileModal";
export function UserMenu() {
const location = useLocation();
const history = useHistory();
- const { isAuthenticated, isGuest, logout, userName } = useClient();
+ const { isAuthenticated, isGuest, logout, userName, client } = useClient();
+ const { displayName } = useDisplayName(client);
+ const { modalState, modalProps } = useModalTriggerState();
const onAction = useCallback(
(value) => {
switch (value) {
case "user":
+ modalState.open();
break;
case "logout":
logout();
@@ -31,7 +36,7 @@ export function UserMenu() {
break;
}
},
- [history, location, logout]
+ [history, location, logout, modalState]
);
const items = useMemo(() => {
@@ -41,7 +46,7 @@ export function UserMenu() {
arr.push({
key: "user",
icon: UserIcon,
- label: userName,
+ label: displayName || userName,
});
}
@@ -67,24 +72,27 @@ export function UserMenu() {
}
return arr;
- }, [isAuthenticated, isGuest, userName]);
+ }, [isAuthenticated, isGuest, userName, displayName]);
return (
-
-
- {(props) => (
-
- )}
-
+ <>
+
+
+ {(props) => (
+
+ )}
+
+ {modalState.isOpen && }
+ >
);
}
diff --git a/src/button/Button.jsx b/src/button/Button.jsx
index fcb3c90e..fe77014b 100644
--- a/src/button/Button.jsx
+++ b/src/button/Button.jsx
@@ -14,6 +14,7 @@ const variantToClassName = {
default: [styles.button],
toolbar: [styles.toolbarButton],
icon: [styles.iconButton],
+ secondary: [styles.secondary],
copy: [styles.copyButton],
iconCopy: [styles.iconCopyButton],
};
diff --git a/src/button/Button.module.css b/src/button/Button.module.css
index 4da1be35..1d687fb7 100644
--- a/src/button/Button.module.css
+++ b/src/button/Button.module.css
@@ -17,7 +17,9 @@ limitations under the License.
.button,
.toolbarButton,
.iconButton,
-.iconCopyButton {
+.iconCopyButton,
+.secondary,
+.copyButton {
position: relative;
display: flex;
justify-content: center;
@@ -28,15 +30,20 @@ limitations under the License.
cursor: pointer;
}
-.button {
- color: #fff;
- background-color: var(--primaryColor);
+.secondary,
+.button,
+.copyButton {
padding: 7px 15px;
border-radius: 8px;
font-size: 14px;
font-weight: 700;
}
+.button {
+ color: #fff;
+ background-color: var(--primaryColor);
+}
+
.toolbarButton {
width: 50px;
height: 50px;
@@ -111,21 +118,17 @@ limitations under the License.
top: calc(100% + 6px);
}
+.secondary,
.copyButton {
- position: relative;
- display: flex;
- justify-content: center;
- align-items: center;
- background-color: transparent;
- cursor: pointer;
- border: 2px solid #0dbd8b;
- border-radius: 8px;
color: #0dbd8b;
+ border: 2px solid #0dbd8b;
+ background-color: transparent;
+}
+
+.copyButton {
width: 100%;
height: 40px;
transition: border-color 250ms, background-color 250ms;
- padding: 0 20px;
- flex-shrink: 0;
}
.copyButton span {