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 }) {
|
||||
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 };
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
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 { 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 (
|
||||
<PopoverMenuTrigger placement="bottom right">
|
||||
<Button variant="icon" className={styles.userButton}>
|
||||
<ButtonTooltip>Profile</ButtonTooltip>
|
||||
<UserIcon />
|
||||
</Button>
|
||||
{(props) => (
|
||||
<Menu {...props} label="User menu" onAction={onAction}>
|
||||
{items.map(({ key, icon: Icon, label }) => (
|
||||
<Item key={key} textValue={label}>
|
||||
<Icon />
|
||||
<span>{label}</span>
|
||||
</Item>
|
||||
))}
|
||||
</Menu>
|
||||
)}
|
||||
</PopoverMenuTrigger>
|
||||
<>
|
||||
<PopoverMenuTrigger placement="bottom right">
|
||||
<Button variant="icon" className={styles.userButton}>
|
||||
<ButtonTooltip>Profile</ButtonTooltip>
|
||||
<UserIcon />
|
||||
</Button>
|
||||
{(props) => (
|
||||
<Menu {...props} label="User menu" onAction={onAction}>
|
||||
{items.map(({ key, icon: Icon, label }) => (
|
||||
<Item key={key} textValue={label}>
|
||||
<Icon />
|
||||
<span>{label}</span>
|
||||
</Item>
|
||||
))}
|
||||
</Menu>
|
||||
)}
|
||||
</PopoverMenuTrigger>
|
||||
{modalState.isOpen && <ProfileModal client={client} {...modalProps} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ const variantToClassName = {
|
|||
default: [styles.button],
|
||||
toolbar: [styles.toolbarButton],
|
||||
icon: [styles.iconButton],
|
||||
secondary: [styles.secondary],
|
||||
copy: [styles.copyButton],
|
||||
iconCopy: [styles.iconCopyButton],
|
||||
};
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Reference in a new issue