diff --git a/src/components/shared/Layout/nav/SettingsMenu.tsx b/src/components/shared/Layout/nav/SettingsMenu.tsx index bd327517..6c5b8a01 100644 --- a/src/components/shared/Layout/nav/SettingsMenu.tsx +++ b/src/components/shared/Layout/nav/SettingsMenu.tsx @@ -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(false); @@ -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( @@ -87,6 +91,19 @@ export function SettingsMenu() { : { vertical: "bottom", horizontal: "left" } } > + {isLoggedIn && ( + { + setMenuOpen(false); + setUsernameDialogOpen(true); + }} + > + + + + Update Username + + )} {isLoggedIn && ( { @@ -170,6 +187,11 @@ export function SettingsMenu() { open={betaTestsOpen} onClose={() => setBetaTestsOpen(false)} /> + setUsernameDialogOpen(false)} + updating + /> ); } diff --git a/src/components/shared/UserAvatar.tsx b/src/components/shared/UserAvatar.tsx index eb2ee00f..13ab8819 100644 --- a/src/components/shared/UserAvatar.tsx +++ b/src/components/shared/UserAvatar.tsx @@ -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) { - - - ; + return ( + + + + ); } + + let shouldShowPhoto = false; + if (forceShowPhoto !== undefined) { + shouldShowPhoto = forceShowPhoto; + } else { + shouldShowPhoto = !user.hidePhoto; + } + return ( ({ bgcolor: theme.palette.darkGrey.dark, color: theme.palette.darkGrey.contrastText, diff --git a/src/components/shared/UserNameDialog.tsx b/src/components/shared/UserNameDialog.tsx index 98b275bd..9e3562ef 100644 --- a/src/components/shared/UserNameDialog.tsx +++ b/src/components/shared/UserNameDialog.tsx @@ -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) => { @@ -44,21 +73,51 @@ export function UserNameDialog(props: UserNameDialogProps) { }; return ( - {}}> - We did not catch your name + {}}> + {updating ? ( + + Change Username + + ) : ( + We did not catch your name + )} - - Add a username so that other players in campaigns you join know who - you are. - - setName(evt.currentTarget.value)} - sx={{ mt: 4 }} - /> + + + Usernames will be displayed to other players and GMs in campaigns. + + setName(evt.currentTarget.value)} + sx={{ mt: 4 }} + /> + {user?.photoURL && ( + + + setShowProfileImage(checked)} + /> + } + /> + + )} + + {updating && ( + + )} { }); } -export function updateUserName(name: string): Promise { +export function updateUser(userDoc: UserDocument): Promise { 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."); diff --git a/src/pages/Campaign/CampaignGMScreenPage/hooks/useSyncStore.ts b/src/pages/Campaign/CampaignGMScreenPage/hooks/useSyncStore.ts index 67314538..25aafb26 100644 --- a/src/pages/Campaign/CampaignGMScreenPage/hooks/useSyncStore.ts +++ b/src/pages/Campaign/CampaignGMScreenPage/hooks/useSyncStore.ts @@ -28,6 +28,9 @@ export function useSyncStore() { useEffect(() => { setCampaignId(campaignId); + return () => { + setCampaignId(undefined); + }; }, [campaignId, setCampaignId]); useListenToCurrentCampaignCharacters(); diff --git a/src/types/User.type.ts b/src/types/User.type.ts index 81bfcc40..7b8d4fc9 100644 --- a/src/types/User.type.ts +++ b/src/types/User.type.ts @@ -1,4 +1,5 @@ export interface UserDocument { displayName: string; photoURL?: string; + hidePhoto?: boolean; }