teams list now renders
This commit is contained in:
parent
17c559bdfc
commit
e95b654d7f
81 changed files with 6132 additions and 40 deletions
719
src/user/ObjectUsers.tsx
Normal file
719
src/user/ObjectUsers.tsx
Normal file
|
|
@ -0,0 +1,719 @@
|
|||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
Button,
|
||||
Checkbox,
|
||||
CircularProgress,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Divider,
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
FormGroup,
|
||||
FormLabel,
|
||||
Grid,
|
||||
Grid2,
|
||||
IconButton,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemAvatar,
|
||||
ListItemSecondaryAction,
|
||||
ListItemText,
|
||||
PaletteColor,
|
||||
Stack,
|
||||
Theme,
|
||||
Tooltip,
|
||||
Typography,
|
||||
useTheme
|
||||
} from "@mui/material";
|
||||
// import { Theme } from "@material-ui/core/styles/createMuiTheme";
|
||||
// import { PaletteColor } from "@material-ui/core/styles/createPalette";
|
||||
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;
|
||||
}
|
||||
|
||||
export default function ObjectUsers(props: Props) {
|
||||
const classes = useStyles();
|
||||
const theme = useTheme();
|
||||
const permissionAPI = usePermissionAPI();
|
||||
const [{ user, team }, dispatch] = useGlobalState();
|
||||
const canProvision = user.allowedTo("provision");
|
||||
const userAPI = useUserAPI();
|
||||
const { error, success, warning } = useSnackbar();
|
||||
const {
|
||||
scope,
|
||||
label,
|
||||
permissions,
|
||||
isDialogOpen,
|
||||
closeDialogCallback,
|
||||
refreshCallback,
|
||||
userCallback,
|
||||
dialog,
|
||||
cardMode
|
||||
//useImitation
|
||||
} = props;
|
||||
const prevPermissions = usePrevious(permissions);
|
||||
const prevIsDialogOpen = usePrevious(isDialogOpen);
|
||||
const [initialUsers, setInitialUsers] = useState<User[]>([]);
|
||||
const [users, setUsers] = useState<User[]>([]);
|
||||
const [canManageUsers, setCanManageUsers] = useState<boolean>(
|
||||
permissions.includes(pond.Permission.PERMISSION_USERS)
|
||||
);
|
||||
const [canShare, setCanShare] = useState<boolean>(
|
||||
permissions.includes(pond.Permission.PERMISSION_SHARE)
|
||||
);
|
||||
const [canShareBilling, setCanShareBilling] = useState<boolean>(
|
||||
permissions.includes(pond.Permission.PERMISSION_SHARE) &&
|
||||
permissions.includes(pond.Permission.PERMISSION_USERS) &&
|
||||
permissions.includes(pond.Permission.PERMISSION_WRITE)
|
||||
);
|
||||
const [removedUsers, setRemovedUsers] = useState<string[]>([]);
|
||||
const [removeSelfDialogIsOpen, setRemoveSelfDialogIsOpen] = useState<boolean>(false);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [isShareObjectDialogOpen, setIsShareObjectDialogOpen] = useState<boolean>(false);
|
||||
const [numOwners, setNumOwners] = useState(0);
|
||||
const [useImitation] = useState<boolean>(
|
||||
props.useImitation !== undefined ? props.useImitation : true
|
||||
);
|
||||
// const [editUserDialog, setEditUserDialog] = useState(false);
|
||||
const [userForDialog, setUserForDialog] = useState<User | undefined>(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<string, pond.Permission>([
|
||||
["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 (
|
||||
// <Grid container direction="column" justify="space-between">
|
||||
// <Grid container direction="row" justify="space-between">
|
||||
// <Grid item xs={8}>
|
||||
// Users
|
||||
// <Typography variant="body2" color="textSecondary">
|
||||
// {label}
|
||||
// </Typography>
|
||||
// </Grid>
|
||||
// <Grid item xs={4} container justify="flex-end">
|
||||
// {canShare && (
|
||||
// <Tooltip title={"Share " + label}>
|
||||
// <IconButton
|
||||
// aria-label="Share"
|
||||
// className={classes.iconButton}
|
||||
// onClick={openShareObjectDialog}>
|
||||
// <ShareIcon className={classes.shareIcon} />
|
||||
// </IconButton>
|
||||
// </Tooltip>
|
||||
// )}
|
||||
// </Grid>
|
||||
// </Grid>
|
||||
// {dialog && (
|
||||
// <Typography variant="body2" color="textSecondary" style={{ marginTop: "4px" }}>
|
||||
// {dialog}
|
||||
// </Typography>
|
||||
// )}
|
||||
// </Grid>
|
||||
<Stack direction="column" spacing={2}>
|
||||
<Stack direction="row" spacing={2} justifyContent="space-between">
|
||||
<Box>
|
||||
Users
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
{label}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box display="flex" justifyContent="flex-end">
|
||||
{canShare && (
|
||||
<Tooltip title={`Share ${label}`}>
|
||||
<IconButton
|
||||
aria-label="Share"
|
||||
onClick={openShareObjectDialog}>
|
||||
<ShareIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Box>
|
||||
</Stack>
|
||||
{dialog && (
|
||||
<Typography variant="body2" color="textSecondary" sx={{ mt: 1 }}>
|
||||
{dialog}
|
||||
</Typography>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
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 (
|
||||
<FormControl component="fieldset" fullWidth className={classes.userPermissions}>
|
||||
<FormLabel component="legend"></FormLabel>
|
||||
<FormGroup>
|
||||
<Grid2 container direction="row" justifyContent={justify} spacing={2}>
|
||||
<Grid2 sx={{ gridColumn: `span ${xs} / span ${sm}` }}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={permissions.includes(pond.Permission.PERMISSION_READ)}
|
||||
onChange={changeUserPermissions(user)}
|
||||
value={pond.Permission.PERMISSION_READ}
|
||||
/>
|
||||
}
|
||||
label="View"
|
||||
labelPlacement="end"
|
||||
/>
|
||||
</Grid2>
|
||||
|
||||
<Grid2 sx={{ gridColumn: `span ${xs} / span ${sm}` }}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
disabled={!canRead}
|
||||
checked={permissions.includes(pond.Permission.PERMISSION_WRITE)}
|
||||
onChange={changeUserPermissions(user)}
|
||||
value={pond.Permission.PERMISSION_WRITE}
|
||||
/>
|
||||
}
|
||||
label="Edit"
|
||||
labelPlacement="end"
|
||||
/>
|
||||
</Grid2>
|
||||
|
||||
<Grid2 sx={{ gridColumn: `span ${xs} / span ${sm}` }}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
disabled={!canRead}
|
||||
checked={permissions.includes(pond.Permission.PERMISSION_SHARE)}
|
||||
onChange={changeUserPermissions(user)}
|
||||
value={pond.Permission.PERMISSION_SHARE}
|
||||
/>
|
||||
}
|
||||
label="Share"
|
||||
labelPlacement="end"
|
||||
/>
|
||||
</Grid2>
|
||||
|
||||
<Grid2 sx={{ gridColumn: `span ${xs} / span ${sm}` }}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
disabled={!canRead}
|
||||
checked={permissions.includes(pond.Permission.PERMISSION_USERS)}
|
||||
onChange={changeUserPermissions(user)}
|
||||
value={pond.Permission.PERMISSION_USERS}
|
||||
/>
|
||||
}
|
||||
label="Manage Users"
|
||||
labelPlacement="end"
|
||||
/>
|
||||
</Grid2>
|
||||
|
||||
<Grid2 sx={{ gridColumn: 'span 6 / span 3' }}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={permissions.includes(pond.Permission.PERMISSION_FILE_MANAGEMENT)}
|
||||
onChange={changeUserPermissions(user)}
|
||||
value={pond.Permission.PERMISSION_FILE_MANAGEMENT}
|
||||
/>
|
||||
}
|
||||
label="Files"
|
||||
labelPlacement="end"
|
||||
/>
|
||||
</Grid2>
|
||||
|
||||
<Grid2 sx={{ gridColumn: `span ${xs} / span ${sm}` }}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
disabled={!canShareBilling}
|
||||
checked={permissions.includes(pond.Permission.PERMISSION_BILLING)}
|
||||
onChange={changeUserPermissions(user)}
|
||||
value={pond.Permission.PERMISSION_BILLING}
|
||||
/>
|
||||
}
|
||||
label="Billing"
|
||||
labelPlacement="end"
|
||||
/>
|
||||
</Grid2>
|
||||
</Grid2>
|
||||
</FormGroup>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
|
||||
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 (
|
||||
<ListItem alignItems="flex-start" >
|
||||
<ListItemAvatar>
|
||||
<Avatar
|
||||
alt={name}
|
||||
src={
|
||||
userSettings.avatar && userSettings.avatar !== "" ? userSettings.avatar : undefined
|
||||
}
|
||||
className={classes.userAvatar}>
|
||||
{!(userSettings.avatar && userSettings.avatar !== "") && name}
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
className={classes.textOverflowEllipsis}
|
||||
disableTypography
|
||||
primary={
|
||||
<Typography component="div" variant="body1" color="textPrimary">
|
||||
{name + (isSelf ? " (you)" : "")}
|
||||
</Typography>
|
||||
}
|
||||
secondary={
|
||||
<React.Fragment>
|
||||
<Typography component="div" variant="caption" color="textSecondary">
|
||||
{user.settings.email}
|
||||
</Typography>
|
||||
<Typography component="div" variant="caption" color="textSecondary">
|
||||
{isRemoved ? "REMOVED " : userRoleFromPermissions(permissions)}
|
||||
</Typography>
|
||||
</React.Fragment>
|
||||
}
|
||||
/>
|
||||
{isSelf && !canShareBilling ? (
|
||||
<ListItemSecondaryAction>
|
||||
<Tooltip title={"Remove yourself from " + label}>
|
||||
<IconButton
|
||||
edge="end"
|
||||
aria-label={"Remove yourself from " + label}
|
||||
onClick={openRemoveSelfDialog}>
|
||||
<RemoveUserIcon className={classes.removeIcon} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</ListItemSecondaryAction>
|
||||
) : (canManageUsers || canProvision) && !isRemoved && !cardMode ? (
|
||||
<ListItemSecondaryAction>
|
||||
<Tooltip title="Revoke user's access">
|
||||
<IconButton
|
||||
edge="end"
|
||||
aria-label="Remove user from device"
|
||||
onClick={() => removeUser(or(user.settings, { id: "" }).id)}>
|
||||
<RemoveUserIcon className={classes.removeIcon} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</ListItemSecondaryAction>
|
||||
) : (
|
||||
(canManageUsers || canProvision) && (
|
||||
<ListItemSecondaryAction>
|
||||
<Tooltip title="Edit user's permissions">
|
||||
<IconButton
|
||||
edge="end"
|
||||
aria-label="Edit user permissions"
|
||||
disabled={!canManageUsers}
|
||||
onClick={() => setUserForDialog(user)}>
|
||||
<EditIcon className={classes.editIcon} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</ListItemSecondaryAction>
|
||||
)
|
||||
)}
|
||||
</ListItem>
|
||||
);
|
||||
};
|
||||
|
||||
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(
|
||||
<React.Fragment key={i}>
|
||||
{userListItem(user)}
|
||||
{!cardMode && userPermissionManagement(user)}
|
||||
<Divider />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return <List>{userListItems}</List>;
|
||||
};
|
||||
|
||||
const content = () => {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Grid2 container justifyContent="center" alignContent="center" className={classes.verticalSpacing}>
|
||||
<CircularProgress />
|
||||
</Grid2>
|
||||
);
|
||||
}
|
||||
|
||||
if (users.length < 1) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Typography
|
||||
variant="h6"
|
||||
align="center"
|
||||
color="textSecondary"
|
||||
className={!cardMode ? classes.verticalSpacing : ""}
|
||||
style={{ padding: cardMode ? 0 : "" }}>
|
||||
No users found.
|
||||
</Typography>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return objectUsersList();
|
||||
};
|
||||
|
||||
const actions = () => {
|
||||
return (
|
||||
<Grid2 container direction="row" justifyContent="space-between">
|
||||
<Grid2 sx={{ gridColumn: `span ${4} ` }} ></Grid2>
|
||||
<Grid2 sx={{ gridColumn: `span ${8} ` }} container justifyContent="flex-end">
|
||||
{!cardMode && (
|
||||
<Button onClick={close} color="primary">
|
||||
Cancel
|
||||
</Button>
|
||||
)}
|
||||
{canManageUsers && !cardMode && (
|
||||
<Button onClick={submit} color="primary" disabled={objectUsersUnchanged()}>
|
||||
{cardMode ? "Update" : "Submit"}
|
||||
</Button>
|
||||
)}
|
||||
</Grid2>
|
||||
</Grid2>
|
||||
);
|
||||
};
|
||||
|
||||
const dialogs = () => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<RemoveSelfFromObject
|
||||
scope={scope}
|
||||
label={label}
|
||||
isDialogOpen={removeSelfDialogIsOpen}
|
||||
closeDialogCallback={closeRemoveSelfDialog}
|
||||
numberOfOwners={numOwners}
|
||||
/>
|
||||
<ShareObject
|
||||
scope={scope}
|
||||
label={label}
|
||||
permissions={permissions}
|
||||
isDialogOpen={isShareObjectDialogOpen}
|
||||
closeDialogCallback={closeShareObjectDialog}
|
||||
useImitation={useImitation}
|
||||
sharedUsers={users}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
if (cardMode) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Typography variant="h6">{title()}</Typography>
|
||||
<div style={{ overflowY: "scroll" }}>
|
||||
<Divider />
|
||||
{content()}
|
||||
{actions()}
|
||||
{dialogs()}
|
||||
<div style={{ marginTop: theme.spacing(2) }} />
|
||||
</div>
|
||||
<ResponsiveDialog
|
||||
open={userForDialog !== undefined}
|
||||
onClose={() => setUserForDialog(undefined)}>
|
||||
<DialogTitle>Change {userForDialog?.name()}'s Permissions?</DialogTitle>
|
||||
<DialogContent>{userForDialog && userPermissionManagement(userForDialog)}</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setUserForDialog(undefined)}>Close</Button>
|
||||
<Button color="primary" onClick={submit}>
|
||||
Submit
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</ResponsiveDialog>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ResponsiveDialog
|
||||
fullWidth
|
||||
maxWidth="xs"
|
||||
open={props.isDialogOpen}
|
||||
onClose={close}
|
||||
aria-labelledby="object-users-dialog">
|
||||
<DialogTitle id="object-users-title">{title()}</DialogTitle>
|
||||
<Divider />
|
||||
<DialogContent className={classes.dialogContent}>{content()}</DialogContent>
|
||||
<DialogActions>{actions()}</DialogActions>
|
||||
{dialogs()}
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
}
|
||||
111
src/user/RemoveSelfFromObject.tsx
Normal file
111
src/user/RemoveSelfFromObject.tsx
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Typography
|
||||
} from "@mui/material";
|
||||
import { Scope, User } from "models";
|
||||
import { pond } from "protobuf-ts/pond";
|
||||
import { useGlobalState, usePermissionAPI, useSnackbar } from "providers";
|
||||
import React from "react";
|
||||
import { useNavigate } from "react-router";
|
||||
|
||||
interface Props {
|
||||
scope: Scope;
|
||||
label: string;
|
||||
isDialogOpen: boolean;
|
||||
closeDialogCallback: (removed: boolean) => void;
|
||||
path?: string;
|
||||
numberOfOwners?: number; //including the number of owners will prevent the user from leaving if they are the final owner
|
||||
}
|
||||
|
||||
export default function RemoveSelfFromObject(props: Props) {
|
||||
const navigate = useNavigate();
|
||||
const permissionAPI = usePermissionAPI();
|
||||
const [{ user }] = useGlobalState();
|
||||
const { error, success, warning } = useSnackbar();
|
||||
const { scope, label, isDialogOpen, closeDialogCallback, path, numberOfOwners } = props;
|
||||
const userID = user.id()
|
||||
|
||||
const close = (removed: boolean) => {
|
||||
closeDialogCallback(removed);
|
||||
};
|
||||
|
||||
const submit = () => {
|
||||
permissionAPI
|
||||
.updatePermissions(scope, [
|
||||
User.create(
|
||||
pond.User.create({
|
||||
settings: pond.UserSettings.create({
|
||||
id: userID
|
||||
}),
|
||||
permissions: []
|
||||
})
|
||||
)
|
||||
])
|
||||
.then((response: any) => {
|
||||
success("Successfully left " + label);
|
||||
close(true);
|
||||
navigate("/" + (path !== undefined ? path : scope.kind) + "s");
|
||||
})
|
||||
.catch((err: any) => {
|
||||
err.response.data.error
|
||||
? warning(err.response.data.error)
|
||||
: error("Error occured when trying to remove yourself from " + label);
|
||||
close(false);
|
||||
});
|
||||
};
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ UI BEGINS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
const removeSelfDialog = () => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<DialogTitle id="remove-self-from-object">{"Leave " + label + "?"}</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography color="textSecondary" align="left" variant="subtitle1">
|
||||
You will no longer be able to access {label}.
|
||||
</Typography>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => close(false)} color="primary">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={submit} color="primary" autoFocus>
|
||||
Leave
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const onlyOwnerDialog = () => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<DialogTitle>{"Cannot Leave"}</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography color="textSecondary" align="left" variant="subtitle1">
|
||||
You are currently the only owner. To leave, share it to another user with full
|
||||
permissions to make them an owner first and then leave.
|
||||
</Typography>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => close(false)} color="primary">
|
||||
Close
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={isDialogOpen}
|
||||
onClose={() => close(false)}
|
||||
aria-labelledby="remove-self-from-object">
|
||||
{numberOfOwners && numberOfOwners <= 1 ? onlyOwnerDialog() : removeSelfDialog()}
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
601
src/user/ShareObject.tsx
Normal file
601
src/user/ShareObject.tsx
Normal file
|
|
@ -0,0 +1,601 @@
|
|||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
CircularProgress,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Divider,
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
FormGroup,
|
||||
FormLabel,
|
||||
IconButton,
|
||||
Link,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemSecondaryAction,
|
||||
ListItemText,
|
||||
Tab,
|
||||
Tabs,
|
||||
TextField,
|
||||
Theme,
|
||||
Tooltip,
|
||||
Typography
|
||||
} from "@mui/material";
|
||||
import { green, red } from "@mui/material/colors";
|
||||
// import { Theme } from "@mui/material/styles/createMuiTheme";
|
||||
import {
|
||||
Add as AddIcon,
|
||||
Link as LinkIcon,
|
||||
LinkOff,
|
||||
RemoveCircle as RemoveCircleIcon
|
||||
} from "@mui/icons-material";
|
||||
// import { MobileDateTimePicker } from "@mui/utils/pi";
|
||||
import { MobileDateTimePicker } from '@mui/x-date-pickers';
|
||||
import ResponsiveDialog from "common/ResponsiveDialog";
|
||||
import { usePermissionAPI, usePrevious, useSnackbar } from "hooks";
|
||||
import { cloneDeep } from "lodash";
|
||||
import { binScope, Scope, ShareableLink, User } from "models";
|
||||
import moment, { Moment } from "moment";
|
||||
import { pond } from "protobuf-ts/pond";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { openSnackbar } from "providers/Snackbar";
|
||||
// import { Status } from "@sentry/react";
|
||||
import { useBinAPI, useGateAPI } from "providers";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => ({
|
||||
dialogContent: {
|
||||
marginTop: theme.spacing(2)
|
||||
},
|
||||
formContainer: {
|
||||
display: "flex",
|
||||
flexWrap: "wrap"
|
||||
},
|
||||
formControl: {
|
||||
margin: theme.spacing(1),
|
||||
minWidth: 160,
|
||||
width: "100%"
|
||||
},
|
||||
emailForm: {
|
||||
paddingBottom: theme.spacing(2)
|
||||
},
|
||||
fabContainer: {
|
||||
position: "relative",
|
||||
minHeight: "64px"
|
||||
},
|
||||
addIcon: {
|
||||
color: green[500],
|
||||
"&:hover": {
|
||||
color: green[600]
|
||||
}
|
||||
},
|
||||
removeIcon: {
|
||||
color: red[500],
|
||||
"&:hover": {
|
||||
color: red[600]
|
||||
}
|
||||
},
|
||||
grey: {
|
||||
color: theme.palette.text.secondary
|
||||
}
|
||||
}));
|
||||
|
||||
interface Props {
|
||||
scope: Scope;
|
||||
label: string;
|
||||
permissions: pond.Permission[];
|
||||
isDialogOpen: boolean;
|
||||
closeDialogCallback: Function;
|
||||
useImitation?: boolean;
|
||||
sharedUsers?: User[]; //including the users that the object/team is already shared to will prevent users from sharing again to an existing user
|
||||
}
|
||||
|
||||
export default function ShareObject(props: Props) {
|
||||
const binAPI = useBinAPI();
|
||||
const classes = useStyles();
|
||||
const permissionAPI = usePermissionAPI();
|
||||
const { info, success, error } = useSnackbar();
|
||||
const { scope, label, permissions, isDialogOpen, closeDialogCallback, sharedUsers } = props;
|
||||
const [email, setEmail] = useState<string>("");
|
||||
const [sharedPermissions, setSharedPermissions] = useState<pond.Permission[]>([
|
||||
pond.Permission.PERMISSION_READ
|
||||
]);
|
||||
const [tab, setTab] = useState<number>(0);
|
||||
const prevTab = usePrevious(tab);
|
||||
const [isLoadingLinks, setIsLoadingLinks] = useState<boolean>(false);
|
||||
const [shareableLinks, setShareableLinks] = useState<ShareableLink[]>([]);
|
||||
const [newLinkExpiration, setNewLinkExpiration] = useState<Moment>(moment().add(1, "week"));
|
||||
const [isNewLinkInfinite, setIsNewLinkInfinite] = useState<boolean>(false);
|
||||
const [openExistingUser, setOpenExistingUser] = useState(false);
|
||||
const [useImitation] = useState<boolean>(
|
||||
props.useImitation !== undefined ? props.useImitation : true
|
||||
);
|
||||
const gateAPI = useGateAPI();
|
||||
|
||||
const share = () => {
|
||||
permissionAPI
|
||||
.shareObject(scope, email, sharedPermissions, useImitation)
|
||||
.then((result: any) => {
|
||||
let shareBins = true;
|
||||
if (result && result.data && result.data.existing) {
|
||||
success(label + " was shared with " + email);
|
||||
} else {
|
||||
success(email + " was sent an email to sign up");
|
||||
shareBins = false;
|
||||
}
|
||||
let successBins = true;
|
||||
if (scope.kind === "binyard" && shareBins) {
|
||||
binAPI.listBins(1000, 0, "asc", "name", scope.key).then(resp => {
|
||||
resp.data.bins.forEach(bin => {
|
||||
if (bin.settings) {
|
||||
let newScope = binScope(bin.settings?.key);
|
||||
permissionAPI.shareObject(newScope, email, sharedPermissions).catch(err => {
|
||||
successBins = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
if (!successBins) {
|
||||
openSnackbar("error", "One or more bins failed to share with " + email);
|
||||
} else {
|
||||
success("Bins shared with " + email);
|
||||
|
||||
}
|
||||
}
|
||||
let successGates = true;
|
||||
if (scope.kind === "terminal" && shareBins) {
|
||||
gateAPI
|
||||
.listGates(
|
||||
1000,
|
||||
0,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
[scope.key],
|
||||
[scope.kind]
|
||||
)
|
||||
.then(resp => {
|
||||
resp.data.gates.forEach(gate => {
|
||||
if (gate) {
|
||||
let newScope = { key: gate.key, kind: "gate" } as Scope;
|
||||
permissionAPI.shareObject(newScope, email, sharedPermissions).catch(err => {
|
||||
successGates = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
if (!successGates) {
|
||||
openSnackbar("error", "One or more Gates failed to share");
|
||||
} else {
|
||||
success("Gates shared");
|
||||
}
|
||||
}
|
||||
closeAfterShare();
|
||||
})
|
||||
.catch((err: any) => {
|
||||
error("Unable to share " + label + " with " + email);
|
||||
close();
|
||||
});
|
||||
};
|
||||
|
||||
const loadShareableLinks = useCallback(() => {
|
||||
setIsLoadingLinks(true);
|
||||
permissionAPI
|
||||
.listShareableLinks(scope)
|
||||
.then((response: any) => {
|
||||
let rawShareableLinks = response.data.links ? response.data.links : [];
|
||||
let rShareableLinks = rawShareableLinks.map((rawShareableLink: any) =>
|
||||
ShareableLink.any(rawShareableLink)
|
||||
);
|
||||
setShareableLinks(rShareableLinks);
|
||||
})
|
||||
.catch((error: any) => {
|
||||
setShareableLinks([]);
|
||||
console.log("Error occured while loading shareable links");
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoadingLinks(false);
|
||||
});
|
||||
}, [permissionAPI, scope]);
|
||||
|
||||
useEffect(() => {
|
||||
if (tab === 1 && prevTab !== tab) {
|
||||
loadShareableLinks();
|
||||
}
|
||||
}, [loadShareableLinks, prevTab, tab]);
|
||||
|
||||
const getNeverExpires = () => {
|
||||
return "";
|
||||
};
|
||||
|
||||
const createShareableLink = () => {
|
||||
const expiration = isNewLinkInfinite ? getNeverExpires() : newLinkExpiration.toISOString();
|
||||
permissionAPI
|
||||
.addShareableLink(scope, expiration)
|
||||
.then((response: any) => {
|
||||
loadShareableLinks();
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.log("Error occured while creating shareable link");
|
||||
});
|
||||
};
|
||||
|
||||
const revokeShareableLink = (code: string) => {
|
||||
permissionAPI
|
||||
.removeShareableLink(scope, code)
|
||||
.then((response: any) => {
|
||||
loadShareableLinks();
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.log("Error occured while removing the shareable link");
|
||||
});
|
||||
};
|
||||
|
||||
const getShareableLinkURL = (code: string): string => {
|
||||
const base = window.location.origin;
|
||||
return base + "/" + scope.kind + "s/" + code;
|
||||
};
|
||||
|
||||
const copyShareableLinkToClipboard = (code: string) => {
|
||||
const url = getShareableLinkURL(code);
|
||||
navigator.clipboard.writeText(url);
|
||||
info("Copied link to clipboard");
|
||||
};
|
||||
|
||||
const closeAfterShare = () => {
|
||||
closeDialogCallback(true);
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
closeDialogCallback(false);
|
||||
};
|
||||
|
||||
const changeEmail = (event: any) => {
|
||||
setEmail(event.target.value);
|
||||
};
|
||||
|
||||
const changePermissions = (event: any) => {
|
||||
let updatedSharedPermissions: Array<pond.Permission> = cloneDeep(sharedPermissions);
|
||||
let permission: pond.Permission;
|
||||
switch (event.target.value) {
|
||||
case "1":
|
||||
permission = pond.Permission.PERMISSION_USERS;
|
||||
break;
|
||||
case "2":
|
||||
permission = pond.Permission.PERMISSION_READ;
|
||||
break;
|
||||
case "3":
|
||||
permission = pond.Permission.PERMISSION_WRITE;
|
||||
break;
|
||||
case "4":
|
||||
permission = pond.Permission.PERMISSION_SHARE;
|
||||
break;
|
||||
case "5":
|
||||
permission = pond.Permission.PERMISSION_BILLING;
|
||||
break;
|
||||
case "6":
|
||||
permission = pond.Permission.PERMISSION_FILE_MANAGEMENT;
|
||||
break;
|
||||
default:
|
||||
permission = pond.Permission.PERMISSION_INVALID;
|
||||
break;
|
||||
}
|
||||
|
||||
if (updatedSharedPermissions.includes(permission)) {
|
||||
updatedSharedPermissions = updatedSharedPermissions.filter(
|
||||
(curPermission: pond.Permission) => curPermission !== permission
|
||||
);
|
||||
} else {
|
||||
updatedSharedPermissions.push(permission);
|
||||
}
|
||||
|
||||
setSharedPermissions(updatedSharedPermissions);
|
||||
};
|
||||
|
||||
const changeTab = (event: any, newTab: number) => {
|
||||
setTab(newTab);
|
||||
};
|
||||
|
||||
const isValid = () => {
|
||||
return email.trim() !== "" && sharedPermissions.length > 0;
|
||||
};
|
||||
|
||||
// UI BEGINS
|
||||
|
||||
const emailForm = () => {
|
||||
return (
|
||||
<TextField
|
||||
id="email"
|
||||
name="email"
|
||||
label="Email"
|
||||
value={email}
|
||||
onChange={changeEmail}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
className={classes.emailForm}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const permissionsForm = () => {
|
||||
return (
|
||||
<FormControl component="fieldset" fullWidth variant="outlined">
|
||||
<FormLabel component="legend">Permissions</FormLabel>
|
||||
<FormGroup>
|
||||
{permissions.includes(pond.Permission.PERMISSION_READ) && (
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
disabled={sharedPermissions.includes(pond.Permission.PERMISSION_READ)}
|
||||
checked={sharedPermissions.includes(pond.Permission.PERMISSION_READ)}
|
||||
onChange={changePermissions}
|
||||
value={pond.Permission.PERMISSION_READ as pond.Permission}
|
||||
/>
|
||||
}
|
||||
label="View"
|
||||
labelPlacement="end"
|
||||
/>
|
||||
)}
|
||||
|
||||
{permissions.includes(pond.Permission.PERMISSION_WRITE) && (
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={sharedPermissions.includes(pond.Permission.PERMISSION_WRITE)}
|
||||
onChange={changePermissions}
|
||||
value={pond.Permission.PERMISSION_WRITE as pond.Permission}
|
||||
/>
|
||||
}
|
||||
label="Edit"
|
||||
labelPlacement="end"
|
||||
/>
|
||||
)}
|
||||
|
||||
{permissions.includes(pond.Permission.PERMISSION_SHARE) && (
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={sharedPermissions.includes(pond.Permission.PERMISSION_SHARE)}
|
||||
onChange={changePermissions}
|
||||
value={pond.Permission.PERMISSION_SHARE as pond.Permission}
|
||||
/>
|
||||
}
|
||||
label="Share"
|
||||
labelPlacement="end"
|
||||
/>
|
||||
)}
|
||||
|
||||
{permissions.includes(pond.Permission.PERMISSION_USERS) && (
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={sharedPermissions.includes(pond.Permission.PERMISSION_USERS)}
|
||||
onChange={changePermissions}
|
||||
value={pond.Permission.PERMISSION_USERS as pond.Permission}
|
||||
/>
|
||||
}
|
||||
label="Manage Users"
|
||||
labelPlacement="end"
|
||||
/>
|
||||
)}
|
||||
|
||||
{permissions.includes(pond.Permission.PERMISSION_FILE_MANAGEMENT) && (
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={sharedPermissions.includes(pond.Permission.PERMISSION_FILE_MANAGEMENT)}
|
||||
onChange={changePermissions}
|
||||
value={pond.Permission.PERMISSION_FILE_MANAGEMENT as pond.Permission}
|
||||
/>
|
||||
}
|
||||
label="Files"
|
||||
labelPlacement="end"
|
||||
/>
|
||||
)}
|
||||
|
||||
{permissions.includes(pond.Permission.PERMISSION_BILLING) && (
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={sharedPermissions.includes(pond.Permission.PERMISSION_BILLING)}
|
||||
onChange={changePermissions}
|
||||
value={pond.Permission.PERMISSION_BILLING as pond.Permission}
|
||||
/>
|
||||
}
|
||||
label="Billing"
|
||||
labelPlacement="end"
|
||||
/>
|
||||
)}
|
||||
</FormGroup>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
|
||||
const emailSharing = () => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{emailForm()}
|
||||
{permissionsForm()}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const existingUserDialog = () => {
|
||||
return (
|
||||
<ResponsiveDialog
|
||||
open={openExistingUser}
|
||||
onClose={() => {
|
||||
setOpenExistingUser(false);
|
||||
close();
|
||||
}}>
|
||||
<DialogTitle>User Already exists</DialogTitle>
|
||||
<DialogContent>This user is already part of {label}.</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setOpenExistingUser(false);
|
||||
close();
|
||||
}}>
|
||||
Close
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
};
|
||||
|
||||
const shareableLinksList = () => {
|
||||
const now = moment();
|
||||
let items: any[] = [];
|
||||
shareableLinks.forEach((link: ShareableLink) => {
|
||||
const expiration = moment(link.settings.expiration);
|
||||
const neverExpires = link.settings.expiration === getNeverExpires();
|
||||
const expired = !neverExpires && expiration < now;
|
||||
items.push(
|
||||
<ListItem key={link.key()}>
|
||||
{expired ? (
|
||||
<IconButton disabled>
|
||||
<LinkOff />
|
||||
</IconButton>
|
||||
) : (
|
||||
<Tooltip title="Click to copy link">
|
||||
<IconButton onClick={() => copyShareableLinkToClipboard(link.key())}>
|
||||
<LinkIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
<ListItemText
|
||||
primary={
|
||||
expired ? (
|
||||
<span className={classes.grey}>{"/devices/" + link.key()}</span>
|
||||
) : (
|
||||
<Link href={getShareableLinkURL(link.key())} target="_blank">
|
||||
{"/devices/" + link.key()}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
secondary={
|
||||
neverExpires ? (
|
||||
"Never expires"
|
||||
) : (
|
||||
<Tooltip title={expiration.calendar()}>
|
||||
<span>{(expired ? "Expired " : "Expires ") + expiration.fromNow()}</span>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
/>
|
||||
<ListItemSecondaryAction>
|
||||
<Tooltip title="Revoke shareable link">
|
||||
<IconButton onClick={() => revokeShareableLink(link.key())}>
|
||||
<RemoveCircleIcon className={classes.removeIcon} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</ListItemSecondaryAction>
|
||||
</ListItem>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<List>
|
||||
{isLoadingLinks ? <CircularProgress /> : <React.Fragment>{items}</React.Fragment>}
|
||||
{shareableLinks.length > 0 && <Divider variant="middle" />}
|
||||
<ListItem>
|
||||
<FormGroup row>
|
||||
<MobileDateTimePicker
|
||||
disabled={isNewLinkInfinite}
|
||||
// inputRef={props => <TextField {...props} helperText="" />}
|
||||
label="Expiration Date"
|
||||
value={newLinkExpiration}
|
||||
onChange={date => setNewLinkExpiration(date as Moment)}
|
||||
disablePast
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={isNewLinkInfinite}
|
||||
onChange={(_, checked) => setIsNewLinkInfinite(checked)}
|
||||
value="isNewLinkInfinite"
|
||||
color="secondary"
|
||||
/>
|
||||
}
|
||||
label="Never expires"
|
||||
/>
|
||||
</FormGroup>
|
||||
<ListItemSecondaryAction>
|
||||
<Tooltip title="Create new shareable link">
|
||||
<IconButton
|
||||
className={classes.addIcon}
|
||||
color="default"
|
||||
onClick={() => createShareableLink()}>
|
||||
<AddIcon className={classes.addIcon} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</ListItemSecondaryAction>
|
||||
</ListItem>
|
||||
</List>
|
||||
);
|
||||
};
|
||||
|
||||
const content = () => {
|
||||
switch (tab) {
|
||||
case 1:
|
||||
return <React.Fragment>{shareableLinksList()}</React.Fragment>;
|
||||
default:
|
||||
return <React.Fragment>{emailSharing()}</React.Fragment>;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{existingUserDialog()}
|
||||
<ResponsiveDialog
|
||||
maxWidth="sm"
|
||||
fullWidth
|
||||
open={isDialogOpen}
|
||||
onClose={close}
|
||||
aria-labelledby="share-dialog-title">
|
||||
<DialogTitle id="share-dialog-title">
|
||||
Share
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
{label}
|
||||
</Typography>
|
||||
</DialogTitle>
|
||||
{scope.kind === "device" && (
|
||||
<Tabs value={tab} onChange={changeTab}>
|
||||
<Tab label="Share by Email" />
|
||||
<Tab label="Shareable Links" />
|
||||
</Tabs>
|
||||
)}
|
||||
<Divider />
|
||||
<DialogContent className={classes.dialogContent}>{content()}</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={close} color="primary">
|
||||
Cancel
|
||||
</Button>
|
||||
{tab === 0 && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
//check here if the email they entered is already in the list of shared users if it was passed in
|
||||
let hasUser = false;
|
||||
sharedUsers?.forEach(user => {
|
||||
if (user.settings.email === email) hasUser = true;
|
||||
});
|
||||
if (hasUser) {
|
||||
setOpenExistingUser(true);
|
||||
} else {
|
||||
share();
|
||||
}
|
||||
}}
|
||||
color="primary"
|
||||
disabled={!isValid()}>
|
||||
Share
|
||||
</Button>
|
||||
)}
|
||||
</DialogActions>
|
||||
</ResponsiveDialog>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue