Passwordless user flow

This commit is contained in:
Robert Long
2021-12-09 12:58:30 -08:00
parent 20350e66a2
commit fc3960ce63
12 changed files with 589 additions and 369 deletions

View File

@@ -14,110 +14,57 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { useCallback, useState } from "react";
import React, { useCallback } from "react";
import { useHistory } from "react-router-dom";
import {
useClient,
useGroupCallRooms,
usePublicRooms,
useCreateRoom,
useCreateRoomAsPasswordlessUser,
} from "./ConferenceCallManagerHooks";
import { Header, HeaderLogo, LeftNav, RightNav } from "./Header";
import styles from "./Home.module.css";
import { FieldRow, InputField, ErrorMessage } from "./Input";
import {
GroupCallIntent,
GroupCallType,
} from "matrix-js-sdk/src/browser-index";
import { UserMenu } from "./UserMenu";
import { Button } from "./button";
import { CallTile } from "./CallTile";
import { CallList } from "./CallList";
import classNames from "classnames";
import { ErrorModal } from "./ErrorModal";
function roomAliasFromRoomName(roomName) {
return roomName
.trim()
.replace(/\s/g, "-")
.replace(/[^\w-]/g, "")
.toLowerCase();
export function Home() {
const { isAuthenticated, isGuest, loading, error, client } = useClient();
if (loading) {
return <div>Loading...</div>;
} else if (error) {
return <ErrorModal error={error} />;
} else if (!isAuthenticated || isGuest) {
return <UnregisteredView />;
} else {
return <RegisteredView client={client} />;
}
}
export function Home({ client, onLogout }) {
function UnregisteredView() {
const history = useHistory();
const [roomName, setRoomName] = useState("");
const [guestAccess, setGuestAccess] = useState(false);
const [createRoomError, setCreateRoomError] = useState();
const rooms = useGroupCallRooms(client);
const publicRooms = usePublicRooms(
client,
import.meta.env.VITE_PUBLIC_SPACE_ROOM_ID
);
const { createRoomError, creatingRoom, createRoom } =
useCreateRoomAsPasswordlessUser();
const onCreateRoom = useCallback(
(e) => {
e.preventDefault();
setCreateRoomError(undefined);
async function createRoom(name, roomAlias, guestAccess) {
const { room_id, room_alias } = await client.createRoom({
visibility: "private",
preset: "public_chat",
name,
room_alias_name: roomAlias,
power_level_content_override: {
invite: 100,
kick: 100,
ban: 100,
redact: 50,
state_default: 0,
events_default: 0,
users_default: 0,
events: {
"m.room.power_levels": 100,
"m.room.history_visibility": 100,
"m.room.tombstone": 100,
"m.room.encryption": 100,
"m.room.name": 50,
"m.room.message": 0,
"m.room.encrypted": 50,
"m.sticker": 50,
"org.matrix.msc3401.call.member": 0,
},
users: {
[client.getUserId()]: 100,
},
},
});
if (guestAccess) {
await client.setGuestAccess(room_id, {
allowJoin: true,
allowRead: true,
});
}
await client.createGroupCall(
room_id,
GroupCallType.Video,
GroupCallIntent.Prompt
);
history.push(`/room/${room_alias || room_id}`);
}
const data = new FormData(e.target);
const roomName = data.get("roomName");
const guestAccess = data.get("guestAccess");
const userName = data.get("userName");
createRoom(roomName, roomAliasFromRoomName(roomName), guestAccess).catch(
(error) => {
setCreateRoomError(error);
setShowAdvanced(true);
}
);
createRoom(roomName, userName).then((roomIdOrAlias) => {
history.push(`/room/${roomIdOrAlias}`);
});
},
[client]
[history]
);
const [roomId, setRoomId] = useState("");
const onJoinRoom = useCallback(
(e) => {
e.preventDefault();
@@ -129,12 +76,134 @@ export function Home({ client, onLogout }) {
);
return (
<div class={styles.home}>
<div className={styles.left}>
<div className={styles.home}>
<div className={classNames(styles.left, styles.fullWidth)}>
<Header>
<LeftNav>
<HeaderLogo />
</LeftNav>
<RightNav>
<UserMenu />
</RightNav>
</Header>
<div className={styles.content}>
<div className={styles.centered}>
<form onSubmit={onJoinRoom}>
<h1>Join a call</h1>
<FieldRow className={styles.fieldRow}>
<InputField
id="roomId"
name="roomId"
label="Call ID"
type="text"
required
autoComplete="off"
placeholder="Call ID"
/>
</FieldRow>
<FieldRow className={styles.fieldRow}>
<Button className={styles.button} type="submit">
Join call
</Button>
</FieldRow>
</form>
<hr />
<form onSubmit={onCreateRoom}>
<h1>Create a call</h1>
<FieldRow className={styles.fieldRow}>
<InputField
id="userName"
name="userName"
label="Username"
type="text"
required
autoComplete="off"
placeholder="Username"
/>
</FieldRow>
<FieldRow className={styles.fieldRow}>
<InputField
id="roomName"
name="roomName"
label="Room Name"
type="text"
required
autoComplete="off"
placeholder="Room Name"
/>
</FieldRow>
{createRoomError && (
<FieldRow className={styles.fieldRow}>
<ErrorMessage>{createRoomError.message}</ErrorMessage>
</FieldRow>
)}
<FieldRow className={styles.fieldRow}>
<Button
className={styles.button}
type="submit"
disabled={creatingRoom}
>
{creatingRoom ? "Creating call..." : "Create call"}
</Button>
</FieldRow>
</form>
</div>
</div>
</div>
</div>
);
}
function RegisteredView({ client }) {
const history = useHistory();
const { createRoomError, creatingRoom, createRoom } = useCreateRoom(client);
const onCreateRoom = useCallback(
(e) => {
e.preventDefault();
const data = new FormData(e.target);
const roomName = data.get("roomName");
createRoom(roomName).then((roomIdOrAlias) => {
history.push(`/room/${roomIdOrAlias}`);
});
},
[history]
);
const onJoinRoom = useCallback(
(e) => {
e.preventDefault();
const data = new FormData(e.target);
const roomId = data.get("roomId");
history.push(`/room/${roomId}`);
},
[history]
);
const publicRooms = usePublicRooms(
client,
import.meta.env.VITE_PUBLIC_SPACE_ROOM_ID
);
const recentRooms = useGroupCallRooms(client);
const hideCallList = publicRooms.length === 0 && recentRooms.length === 0;
return (
<div className={styles.home}>
<div
className={classNames(styles.left, {
[styles.fullWidth]: hideCallList,
})}
>
<Header>
<LeftNav>
<HeaderLogo />
</LeftNav>
{hideCallList && (
<RightNav>
<UserMenu />
</RightNav>
)}
</Header>
<div className={styles.content}>
<div className={styles.centered}>
@@ -149,8 +218,6 @@ export function Home({ client, onLogout }) {
required
autoComplete="off"
placeholder="Call ID"
value={roomId}
onChange={(e) => setRoomId(e.target.value)}
/>
</FieldRow>
<FieldRow className={styles.fieldRow}>
@@ -171,18 +238,6 @@ export function Home({ client, onLogout }) {
required
autoComplete="off"
placeholder="Room Name"
value={roomName}
onChange={(e) => setRoomName(e.target.value)}
/>
</FieldRow>
<FieldRow>
<InputField
id="guestAccess"
name="guestAccess"
label="Allow Guest Access"
type="checkbox"
checked={guestAccess}
onChange={(e) => setGuestAccess(e.target.checked)}
/>
</FieldRow>
{createRoomError && (
@@ -191,55 +246,36 @@ export function Home({ client, onLogout }) {
</FieldRow>
)}
<FieldRow className={styles.fieldRow}>
<Button className={styles.button} type="submit">
Create call
<Button
className={styles.button}
type="submit"
disabled={creatingRoom}
>
{creatingRoom ? "Creating call..." : "Create call"}
</Button>
</FieldRow>
</form>
</div>
</div>
</div>
<div className={styles.right}>
<Header>
<LeftNav />
<RightNav>
<UserMenu
signedIn
userName={client.getUserIdLocalpart()}
onLogout={onLogout}
/>
</RightNav>
</Header>
<div className={styles.content}>
{publicRooms.length > 0 && (
<>
<h3>Public Calls</h3>
<div className={styles.roomList}>
{publicRooms.map((room) => (
<CallTile
key={room.room_id}
name={room.name}
avatarUrl={null}
roomUrl={`/room/${room.room_id}`}
/>
))}
</div>
</>
)}
<h3>Recent Calls</h3>
<div className={styles.roomList}>
{rooms.map(({ room, participants }) => (
<CallTile
key={room.roomId}
name={room.name}
avatarUrl={null}
roomUrl={`/room/${room.getCanonicalAlias() || room.roomId}`}
participants={participants}
/>
))}
{!hideCallList && (
<div className={styles.right}>
<Header>
<LeftNav />
<RightNav>
<UserMenu />
</RightNav>
</Header>
<div className={styles.content}>
{publicRooms.length > 0 && (
<CallList title="Public Calls" rooms={publicRooms} />
)}
{recentRooms.length > 0 && (
<CallList title="Recent Calls" rooms={recentRooms} />
)}
</div>
</div>
</div>
)}
</div>
);
}