Add profile modal
This commit is contained in:
parent
e08a24ade7
commit
2e97c488e2
8 changed files with 203 additions and 44 deletions
|
@ -104,7 +104,15 @@ export async function fetchGroupCall(
|
||||||
export function ClientProvider({ homeserverUrl, children }) {
|
export function ClientProvider({ homeserverUrl, children }) {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const [
|
const [
|
||||||
{ loading, isAuthenticated, isPasswordlessUser, isGuest, client, userName },
|
{
|
||||||
|
loading,
|
||||||
|
isAuthenticated,
|
||||||
|
isPasswordlessUser,
|
||||||
|
isGuest,
|
||||||
|
client,
|
||||||
|
userName,
|
||||||
|
displayName,
|
||||||
|
},
|
||||||
setState,
|
setState,
|
||||||
] = useState({
|
] = useState({
|
||||||
loading: true,
|
loading: true,
|
||||||
|
@ -113,6 +121,7 @@ export function ClientProvider({ homeserverUrl, children }) {
|
||||||
isGuest: false,
|
isGuest: false,
|
||||||
client: undefined,
|
client: undefined,
|
||||||
userName: null,
|
userName: null,
|
||||||
|
displayName: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -602,3 +611,69 @@ export function getRoomUrl(roomId) {
|
||||||
return `${window.location.host}/room/${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 };
|
||||||
|
}
|
||||||
|
|
|
@ -15,15 +15,11 @@
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fieldRow > .field {
|
.fieldRow > * {
|
||||||
margin: 0 5px;
|
margin-right: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fieldRow > .field:first-child {
|
.fieldRow > :last-child {
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fieldRow > .field:last-child {
|
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.15);
|
box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.15);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
max-width: 90vw;
|
max-width: 90vw;
|
||||||
min-width: 288px;
|
width: 600px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modalHeader {
|
.modalHeader {
|
||||||
|
|
76
src/ProfileModal.jsx
Normal file
76
src/ProfileModal.jsx
Normal file
|
@ -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 (
|
||||||
|
<Modal title="Profile" isDismissable {...rest}>
|
||||||
|
<ModalContent>
|
||||||
|
<form onSubmit={onSubmit}>
|
||||||
|
<FieldRow>
|
||||||
|
<InputField
|
||||||
|
id="displayName"
|
||||||
|
name="displayName"
|
||||||
|
label="Display Name"
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
autoComplete="off"
|
||||||
|
placeholder="Display Name"
|
||||||
|
value={displayName}
|
||||||
|
onChange={onChangeDisplayName}
|
||||||
|
/>
|
||||||
|
</FieldRow>
|
||||||
|
{error && (
|
||||||
|
<FieldRow>
|
||||||
|
<ErrorMessage>{error.message}</ErrorMessage>
|
||||||
|
</FieldRow>
|
||||||
|
)}
|
||||||
|
<FieldRow rightAlign>
|
||||||
|
<Button type="button" variant="secondary" onPress={onClose}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button type="submit" disabled={loading}>
|
||||||
|
{loading ? "Saving..." : "Save"}
|
||||||
|
</Button>
|
||||||
|
</FieldRow>
|
||||||
|
</form>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
0
src/ProfileModal.module.css
Normal file
0
src/ProfileModal.module.css
Normal file
|
@ -8,17 +8,22 @@ import styles from "./UserMenu.module.css";
|
||||||
import { Item } from "@react-stately/collections";
|
import { Item } from "@react-stately/collections";
|
||||||
import { Menu } from "./Menu";
|
import { Menu } from "./Menu";
|
||||||
import { useHistory, useLocation } from "react-router-dom";
|
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() {
|
export function UserMenu() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const history = useHistory();
|
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(
|
const onAction = useCallback(
|
||||||
(value) => {
|
(value) => {
|
||||||
switch (value) {
|
switch (value) {
|
||||||
case "user":
|
case "user":
|
||||||
|
modalState.open();
|
||||||
break;
|
break;
|
||||||
case "logout":
|
case "logout":
|
||||||
logout();
|
logout();
|
||||||
|
@ -31,7 +36,7 @@ export function UserMenu() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[history, location, logout]
|
[history, location, logout, modalState]
|
||||||
);
|
);
|
||||||
|
|
||||||
const items = useMemo(() => {
|
const items = useMemo(() => {
|
||||||
|
@ -41,7 +46,7 @@ export function UserMenu() {
|
||||||
arr.push({
|
arr.push({
|
||||||
key: "user",
|
key: "user",
|
||||||
icon: UserIcon,
|
icon: UserIcon,
|
||||||
label: userName,
|
label: displayName || userName,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,24 +72,27 @@ export function UserMenu() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return arr;
|
return arr;
|
||||||
}, [isAuthenticated, isGuest, userName]);
|
}, [isAuthenticated, isGuest, userName, displayName]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PopoverMenuTrigger placement="bottom right">
|
<>
|
||||||
<Button variant="icon" className={styles.userButton}>
|
<PopoverMenuTrigger placement="bottom right">
|
||||||
<ButtonTooltip>Profile</ButtonTooltip>
|
<Button variant="icon" className={styles.userButton}>
|
||||||
<UserIcon />
|
<ButtonTooltip>Profile</ButtonTooltip>
|
||||||
</Button>
|
<UserIcon />
|
||||||
{(props) => (
|
</Button>
|
||||||
<Menu {...props} label="User menu" onAction={onAction}>
|
{(props) => (
|
||||||
{items.map(({ key, icon: Icon, label }) => (
|
<Menu {...props} label="User menu" onAction={onAction}>
|
||||||
<Item key={key} textValue={label}>
|
{items.map(({ key, icon: Icon, label }) => (
|
||||||
<Icon />
|
<Item key={key} textValue={label}>
|
||||||
<span>{label}</span>
|
<Icon />
|
||||||
</Item>
|
<span>{label}</span>
|
||||||
))}
|
</Item>
|
||||||
</Menu>
|
))}
|
||||||
)}
|
</Menu>
|
||||||
</PopoverMenuTrigger>
|
)}
|
||||||
|
</PopoverMenuTrigger>
|
||||||
|
{modalState.isOpen && <ProfileModal client={client} {...modalProps} />}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ const variantToClassName = {
|
||||||
default: [styles.button],
|
default: [styles.button],
|
||||||
toolbar: [styles.toolbarButton],
|
toolbar: [styles.toolbarButton],
|
||||||
icon: [styles.iconButton],
|
icon: [styles.iconButton],
|
||||||
|
secondary: [styles.secondary],
|
||||||
copy: [styles.copyButton],
|
copy: [styles.copyButton],
|
||||||
iconCopy: [styles.iconCopyButton],
|
iconCopy: [styles.iconCopyButton],
|
||||||
};
|
};
|
||||||
|
|
|
@ -17,7 +17,9 @@ limitations under the License.
|
||||||
.button,
|
.button,
|
||||||
.toolbarButton,
|
.toolbarButton,
|
||||||
.iconButton,
|
.iconButton,
|
||||||
.iconCopyButton {
|
.iconCopyButton,
|
||||||
|
.secondary,
|
||||||
|
.copyButton {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
@ -28,15 +30,20 @@ limitations under the License.
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
.secondary,
|
||||||
color: #fff;
|
.button,
|
||||||
background-color: var(--primaryColor);
|
.copyButton {
|
||||||
padding: 7px 15px;
|
padding: 7px 15px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
color: #fff;
|
||||||
|
background-color: var(--primaryColor);
|
||||||
|
}
|
||||||
|
|
||||||
.toolbarButton {
|
.toolbarButton {
|
||||||
width: 50px;
|
width: 50px;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
|
@ -111,21 +118,17 @@ limitations under the License.
|
||||||
top: calc(100% + 6px);
|
top: calc(100% + 6px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.secondary,
|
||||||
.copyButton {
|
.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;
|
color: #0dbd8b;
|
||||||
|
border: 2px solid #0dbd8b;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyButton {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
transition: border-color 250ms, background-color 250ms;
|
transition: border-color 250ms, background-color 250ms;
|
||||||
padding: 0 20px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.copyButton span {
|
.copyButton span {
|
||||||
|
|
Loading…
Add table
Reference in a new issue