import { Avatar, Box, Button, Checkbox, CircularProgress, DialogActions, DialogContent, DialogTitle, Divider, FormControl, FormControlLabel, FormGroup, FormLabel, Grid2, IconButton, List, ListItem, ListItemAvatar, ListItemSecondaryAction, ListItemText, PaletteColor, Stack, Theme, Tooltip, Typography, useTheme } from "@mui/material"; import RemoveUserIcon from "@mui/icons-material/RemoveCircle"; import EditIcon from "@mui/icons-material/EditOutlined"; import ShareIcon from "@mui/icons-material/Share"; import ResponsiveDialog from "common/ResponsiveDialog"; import { usePermissionAPI, usePrevious, useSnackbar, useUserAPI } from "hooks"; import { cloneDeep, isEqual } from "lodash"; import { Scope, User } from "models"; import { sortUsersByRole, userRoleFromPermissions } from "pbHelpers/User"; import { pond } from "protobuf-ts/pond"; import React, { useCallback, useEffect, useState } from "react"; import { or } from "utils/types"; import RemoveSelfFromObject from "user/RemoveSelfFromObject"; import ShareObject from "user/ShareObject"; import { useGlobalState } from "providers"; import { red, blue } from "@mui/material/colors"; import { makeStyles } from "@mui/styles"; const useStyles = makeStyles((theme: Theme) => { const avatarBG = theme.palette.secondary["700" as keyof PaletteColor]; return { dialogContent: { padding: `${theme.spacing(1)}px ${theme.spacing(2)}px`, [theme.breakpoints.up("sm")]: { padding: `${theme.spacing(1)}px ${theme.spacing(3)}px` } }, userAvatar: { color: "#fff", backgroundColor: avatarBG }, userPermissions: { paddingLeft: theme.spacing(4), paddingBottom: theme.spacing(1) }, textOverflowEllipsis: { whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }, removeIcon: { color: red["500"], "&:hover": { color: red["600"] } }, verticalSpacing: { paddingTop: theme.spacing(4), paddingBottom: theme.spacing(3) }, lessSpacing: { paddingTop: theme.spacing(1), paddingBottom: theme.spacing(1) }, iconButton: { margin: theme.spacing(1) }, shareIcon: { color: blue["600"], "&:hover": { color: blue["700"] } }, editIcon: { color: blue["300"], "&:hover": { color: blue["400"] } } }; }); interface Props { scope: Scope; label: string; permissions: pond.Permission[]; isDialogOpen: boolean; closeDialogCallback: Function; refreshCallback: Function; userCallback?: (users?: User[] | undefined) => void; dialog?: string; cardMode?: boolean; useImitation?: boolean; shareLabel?: string; // s string to replace the share icon button with a labelled button } export default function ObjectUsers(props: Props) { const classes = useStyles(); const theme = useTheme(); const permissionAPI = usePermissionAPI(); const [{ user, team }, dispatch] = useGlobalState(); const isAdmin = user.hasAdmin(); const userAPI = useUserAPI(); const { error, success, warning } = useSnackbar(); const { scope, label, permissions, isDialogOpen, closeDialogCallback, refreshCallback, userCallback, dialog, cardMode, //useImitation shareLabel } = props; const prevPermissions = usePrevious(permissions); const prevIsDialogOpen = usePrevious(isDialogOpen); const [initialUsers, setInitialUsers] = useState([]); const [users, setUsers] = useState([]); const [canManageUsers, setCanManageUsers] = useState( permissions.includes(pond.Permission.PERMISSION_USERS) ); const [canShare, setCanShare] = useState( permissions.includes(pond.Permission.PERMISSION_SHARE) ); const [canShareBilling, setCanShareBilling] = useState( permissions.includes(pond.Permission.PERMISSION_SHARE) && permissions.includes(pond.Permission.PERMISSION_USERS) && permissions.includes(pond.Permission.PERMISSION_WRITE) ); const [removedUsers, setRemovedUsers] = useState([]); const [removeSelfDialogIsOpen, setRemoveSelfDialogIsOpen] = useState(false); const [isLoading, setIsLoading] = useState(false); const [isShareObjectDialogOpen, setIsShareObjectDialogOpen] = useState(false); const [numOwners, setNumOwners] = useState(0); const [useImitation] = useState( props.useImitation !== undefined ? props.useImitation : true ); // const [editUserDialog, setEditUserDialog] = useState(false); const [userForDialog, setUserForDialog] = useState(undefined); const setDefaultState = () => { setInitialUsers([]); setUsers([]); setRemovedUsers([]); }; const load = useCallback(() => { setIsLoading(true); userAPI .listObjectUsers(scope) .then((response: any) => { let rUsers: User[] = []; or(response.data, { users: [] }).users.forEach((user: any) => { rUsers.push(User.any(user)); }); rUsers = rUsers.filter(u => u.permissions.length > 0); setInitialUsers(cloneDeep(rUsers)); let numOwners = 0; rUsers.forEach(user => { if (userRoleFromPermissions(user.permissions) === "Owner") numOwners++; }); setNumOwners(numOwners); setUsers(rUsers); }) .catch((err: any) => { console.log(err); setInitialUsers([]); setUsers([]); }) .finally(() => setIsLoading(false)); }, [scope, userAPI]); useEffect(() => { if (!prevIsDialogOpen && isDialogOpen) { load(); } if (prevPermissions !== permissions) { setCanManageUsers(permissions.includes(pond.Permission.PERMISSION_USERS)); setCanShare(permissions.includes(pond.Permission.PERMISSION_SHARE)); setCanShareBilling( permissions.includes(pond.Permission.PERMISSION_SHARE) && permissions.includes(pond.Permission.PERMISSION_USERS) && permissions.includes(pond.Permission.PERMISSION_WRITE) ); } }, [isDialogOpen, load, permissions, prevIsDialogOpen, prevPermissions]); const closeRemoveSelfDialog = () => { setRemoveSelfDialogIsOpen(false); }; const close = () => { closeRemoveSelfDialog(); closeDialogCallback(); setDefaultState(); }; const submit = () => { console.log(users); permissionAPI .updatePermissions(scope, users) .then((_response: any) => { success("Users were sucessfully updated for " + label); close(); refreshCallback(); userCallback && userCallback(users); users.forEach(u => { if (u.id() === user.id()) { if (scope.kind === "team" && scope.key === team.key()) { dispatch({ key: "userTeamPermissions", value: u.permissions }); } } }); if (cardMode) { load(); } }) .catch((err: any) => { err.response.data.error ? warning(err.response.data.error) : error("Error occured when updating users for " + label); close(); }) .finally(() => setUserForDialog(undefined)); }; const changeUserPermissions = (user: pond.IUser) => (event: any) => { let updatedUsers = cloneDeep(users); let permissionMapping = new Map([ ["2", pond.Permission.PERMISSION_READ], ["3", pond.Permission.PERMISSION_WRITE], ["4", pond.Permission.PERMISSION_SHARE], ["1", pond.Permission.PERMISSION_USERS], ["5", pond.Permission.PERMISSION_BILLING], ["6", pond.Permission.PERMISSION_FILE_MANAGEMENT] ]); let permission = or( permissionMapping.get(event.target.value), pond.Permission.PERMISSION_INVALID ); for (let i = 0; i < updatedUsers.length; i++) { let currUser = updatedUsers[i]; if ( user && user.settings && currUser && currUser.settings && currUser.settings.id === user.settings.id ) { let permissions = user.permissions ? cloneDeep(user.permissions) : []; if (permissions.includes(permission)) { permissions = permissions.filter( (curPermission: pond.Permission) => curPermission !== permission ); } else { permissions.push(permission); } updatedUsers[i].permissions = permissions; user.permissions = permissions; break; } } setUsers(updatedUsers); }; const removeUser = (user: string) => { let updatedUsers = cloneDeep(users); let updatedRemovedUsers = cloneDeep(removedUsers); for (let i = 0; i < updatedUsers.length; i++) { let currUser = updatedUsers[i]; if (currUser && currUser.settings && currUser.settings.id === user) { updatedRemovedUsers.push(currUser.settings.id); updatedUsers[i].permissions = []; break; } } setUsers(updatedUsers); setRemovedUsers(updatedRemovedUsers); }; const checkIsSelf = (checkUser: pond.IUser): boolean => { const id = checkUser && checkUser.settings ? checkUser.settings.id : ""; return user.id() === id; }; const userIsRemoved = (user: pond.IUser): boolean => { const id = user && user.settings ? user.settings.id : ""; return removedUsers.includes(or(id, "")); }; const objectUsersUnchanged = (): boolean => { return isEqual(initialUsers, users); }; const openRemoveSelfDialog = () => { setRemoveSelfDialogIsOpen(true); }; const openShareObjectDialog = () => { setIsShareObjectDialogOpen(true); }; const closeShareObjectDialog = (shared: boolean | undefined) => { setIsShareObjectDialogOpen(false); if (shared) { load(); } }; // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ UI BEGINS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ const title = () => { return ( // // // // Users // // {label} // // // // {canShare && ( // // // // // // )} // // // {dialog && ( // // {dialog} // // )} // Users {label} {canShare && ( {shareLabel ? : } )} {dialog && ( {dialog} )} ); }; const userPermissionManagement = (user: User) => { let permissions = user.permissions; if ( !canManageUsers || (checkIsSelf(user) && !canShareBilling) || userIsRemoved(user) || (permissions.includes(pond.Permission.PERMISSION_USERS) && permissions.includes(pond.Permission.PERMISSION_READ) && permissions.includes(pond.Permission.PERMISSION_WRITE) && permissions.includes(pond.Permission.PERMISSION_SHARE) && permissions.includes(pond.Permission.PERMISSION_BILLING) && permissions.includes(pond.Permission.PERMISSION_FILE_MANAGEMENT) && !cardMode) ) { return null; } const canRead = permissions.includes(pond.Permission.PERMISSION_READ); const xs = cardMode ? 12 : 4; const sm = cardMode ? 4 : 3; const justify = cardMode ? "flex-start" : "center"; return ( } label="View" labelPlacement="end" /> } label="Edit" labelPlacement="end" /> } label="Share" labelPlacement="end" /> } label="Manage Users" labelPlacement="end" /> } label="Files" labelPlacement="end" /> } label="Billing" labelPlacement="end" /> ); }; const userListItem = (user: User) => { let isSelf = checkIsSelf(user); let isRemoved = userIsRemoved(user); let name = user.name(); let permissions = user.permissions; let userSettings = pond.UserSettings.create(user.settings !== null ? user.settings : undefined); return ( {!(userSettings.avatar && userSettings.avatar !== "") && name} {name + (isSelf ? " (you)" : "")} } secondary={ {user.settings.email} {isRemoved ? "REMOVED " : userRoleFromPermissions(permissions)} } /> {isSelf && !canShareBilling ? ( ) : (canManageUsers || isAdmin) && !isRemoved && !cardMode ? ( removeUser(or(user.settings, { id: "" }).id)}> ) : ( (canManageUsers || isAdmin) && ( setUserForDialog(user)}> ) )} ); }; const objectUsersList = () => { const sortedUsers = sortUsersByRole(users); let userListItems: any = []; for (var i = 0; i < sortedUsers.length; i++) { let user = sortedUsers[i]; if (user) { userListItems.push( {userListItem(user)} {!cardMode && userPermissionManagement(user)} ); } } return {userListItems}; }; const content = () => { if (isLoading) { return ( ); } if (users.length < 1) { return ( No users found. ); } return objectUsersList(); }; const actions = () => { return ( {!cardMode && ( )} {canManageUsers && !cardMode && ( )} ); }; const dialogs = () => { return ( ); }; if (cardMode) { return ( {title()}
{content()} {actions()} {dialogs()}
setUserForDialog(undefined)}> Change {userForDialog?.name()}'s Permissions? {userForDialog && userPermissionManagement(userForDialog)} ); } return ( {title()} {content()} {actions()} {dialogs()} ); }