Skip to content

Commit

Permalink
fix(names): allow users to change names and hide photos
Browse files Browse the repository at this point in the history
  • Loading branch information
scottbenton committed Apr 10, 2024
1 parent 2f7d8f6 commit 88cc40e
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 26 deletions.
22 changes: 22 additions & 0 deletions src/components/shared/Layout/nav/SettingsMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import TestsIcon from "@mui/icons-material/AutoAwesome";
import { useIsMobile } from "hooks/useIsMobile";
import { useStore } from "stores/store";
import { AUTH_STATE } from "stores/auth/auth.slice.type";
import { UserNameDialog } from "components/shared/UserNameDialog";
import UsernameIcon from "@mui/icons-material/AccountCircle";

export function SettingsMenu() {
const [menuOpen, setMenuOpen] = useState<boolean>(false);
Expand All @@ -43,6 +45,8 @@ export function SettingsMenu() {
useState(false);

const [betaTestsOpen, setBetaTestsOpen] = useState(false);
const [usernameDialogOpen, setUsernameDialogOpen] = useState(false);

const isMobile = useIsMobile();

const isLoggedIn = useStore(
Expand Down Expand Up @@ -87,6 +91,19 @@ export function SettingsMenu() {
: { vertical: "bottom", horizontal: "left" }
}
>
{isLoggedIn && (
<MenuItem
onClick={() => {
setMenuOpen(false);
setUsernameDialogOpen(true);
}}
>
<ListItemIcon>
<UsernameIcon />
</ListItemIcon>
<ListItemText>Update Username</ListItemText>
</MenuItem>
)}
{isLoggedIn && (
<MenuItem
onClick={() => {
Expand Down Expand Up @@ -170,6 +187,11 @@ export function SettingsMenu() {
open={betaTestsOpen}
onClose={() => setBetaTestsOpen(false)}
/>
<UserNameDialog
open={usernameDialogOpen}
handleClose={() => setUsernameDialogOpen(false)}
updating
/>
</>
);
}
24 changes: 18 additions & 6 deletions src/components/shared/UserAvatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,37 @@ import { Avatar, Skeleton } from "@mui/material";

export interface UserAvatarProps {
uid: string;
forceName?: string;
forceShowPhoto?: boolean;
}

export function UserAvatar(props: UserAvatarProps) {
const { uid } = props;
const { uid, forceShowPhoto, forceName } = props;
const user = useStore((store) => store.users.userMap[uid]?.doc);
const loadUser = useStore((store) => store.users.loadUserDocument);
useEffect(() => {
loadUser(uid);
}, [loadUser, uid]);

const initials = getInitials(user?.displayName ?? "User");
const initials = getInitials(forceName ?? user?.displayName ?? "User");
if (!user) {
<Skeleton variant={"circular"}>
<Avatar />
</Skeleton>;
return (
<Skeleton variant={"circular"}>
<Avatar />
</Skeleton>
);
}

let shouldShowPhoto = false;
if (forceShowPhoto !== undefined) {
shouldShowPhoto = forceShowPhoto;
} else {
shouldShowPhoto = !user.hidePhoto;
}

return (
<Avatar
src={user?.photoURL ?? undefined}
src={shouldShowPhoto && user.photoURL ? user.photoURL : undefined}
sx={(theme) => ({
bgcolor: theme.palette.darkGrey.dark,
color: theme.palette.darkGrey.contrastText,
Expand Down
93 changes: 76 additions & 17 deletions src/components/shared/UserNameDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,62 @@
import {
Button,
Checkbox,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
FormControlLabel,
Stack,
TextField,
Typography,
} from "@mui/material";
import { LoadingButton } from "@mui/lab";
import { useSnackbar } from "providers/SnackbarProvider/useSnackbar";
import { updateUserName } from "lib/auth.lib";
import { useState } from "react";
import { updateUser } from "lib/auth.lib";
import { useEffect, useState } from "react";
import { useStore } from "stores/store";
import { UserAvatar } from "./UserAvatar";
import { UserDocument } from "types/User.type";
import { DialogTitleWithCloseButton } from "./DialogTitleWithCloseButton";

export interface UserNameDialogProps {
open: boolean;
updating?: boolean;
handleClose: () => void;
}

export function UserNameDialog(props: UserNameDialogProps) {
const { open, handleClose } = props;
const { open, updating, handleClose } = props;
const { error } = useSnackbar();

const [name, setName] = useState("");
const user = useStore((store) => store.auth.user);
const hasHiddenPhotoUrl = useStore((store) =>
user?.uid ? store.users.userMap[user.uid]?.doc?.hidePhoto ?? false : false
);

const [name, setName] = useState(user?.displayName ?? "");
const [showProfileImage, setShowProfileImage] = useState(!hasHiddenPhotoUrl);

useEffect(() => {
setShowProfileImage(!hasHiddenPhotoUrl);
}, [hasHiddenPhotoUrl]);

const [isLoading, setIsLoading] = useState(false);

const handleSave = () => {
if (name.trim().length > 0) {
const newUserDoc: UserDocument = {
displayName: name,
hidePhoto: !showProfileImage,
};
if (user?.photoURL) {
newUserDoc.photoURL = user.photoURL;
}

setIsLoading(true);
updateUserName(name)
updateUser(newUserDoc)
.then(() => {
location.reload();
handleClose();
})
.catch((e) => {
Expand All @@ -44,21 +73,51 @@ export function UserNameDialog(props: UserNameDialogProps) {
};

return (
<Dialog open={open} onClose={() => {}}>
<DialogTitle>We did not catch your name</DialogTitle>
<Dialog open={open} onClose={updating ? handleClose : () => {}}>
{updating ? (
<DialogTitleWithCloseButton onClose={handleClose}>
Change Username
</DialogTitleWithCloseButton>
) : (
<DialogTitle>We did not catch your name</DialogTitle>
)}
<DialogContent>
<Typography>
Add a username so that other players in campaigns you join know who
you are.
</Typography>
<TextField
label={"Username"}
value={name}
onChange={(evt) => setName(evt.currentTarget.value)}
sx={{ mt: 4 }}
/>
<Stack spacing={2}>
<Typography>
Usernames will be displayed to other players and GMs in campaigns.
</Typography>
<TextField
label={"Username"}
value={name}
onChange={(evt) => setName(evt.currentTarget.value)}
sx={{ mt: 4 }}
/>
{user?.photoURL && (
<Stack direction={"row"} spacing={1}>
<UserAvatar
uid={user.uid}
forceShowPhoto={showProfileImage}
forceName={name}
/>
<FormControlLabel
label={"Show Profile Image"}
control={
<Checkbox
checked={showProfileImage}
onChange={(evt, checked) => setShowProfileImage(checked)}
/>
}
/>
</Stack>
)}
</Stack>
</DialogContent>
<DialogActions>
{updating && (
<Button onClick={handleClose} color={"inherit"}>
Cancel
</Button>
)}
<LoadingButton
loading={isLoading}
variant={"contained"}
Expand Down
20 changes: 17 additions & 3 deletions src/lib/auth.lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
import { firebaseAuth } from "../config/firebase.config";
import { BASE_ROUTES, basePaths } from "routes";
import { getErrorMessage } from "functions/getErrorMessage";
import { updateUserDoc } from "api-calls/user/updateUserDoc";
import { UserDocument } from "types/User.type";

const googleAuthProvider = new GoogleAuthProvider();

Expand Down Expand Up @@ -119,15 +121,27 @@ export function getUser(): Promise<User | null> {
});
}

export function updateUserName(name: string): Promise<void> {
export function updateUser(userDoc: UserDocument): Promise<void> {
return new Promise((resolve, reject) => {
const user = firebaseAuth.currentUser;
if (!user) {
reject(new Error("User is not logged in."));
return;
}
updateProfile(user, { displayName: name })
.then(() => resolve())
updateProfile(user, { displayName: userDoc.displayName })
.then(() => {
updateUserDoc({
uid: user.uid,
user: userDoc,
})
.then(() => {
resolve();
})
.catch((e) => {
console.error(e);
reject("Failed to update user.");
});
})
.catch((e) => {
console.error(e);
reject("Failed to update user name.");
Expand Down
3 changes: 3 additions & 0 deletions src/pages/Campaign/CampaignGMScreenPage/hooks/useSyncStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ export function useSyncStore() {

useEffect(() => {
setCampaignId(campaignId);
return () => {
setCampaignId(undefined);
};
}, [campaignId, setCampaignId]);

useListenToCurrentCampaignCharacters();
Expand Down
1 change: 1 addition & 0 deletions src/types/User.type.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export interface UserDocument {
displayName: string;
photoURL?: string;
hidePhoto?: boolean;
}

0 comments on commit 88cc40e

Please sign in to comment.