Skip to content

Commit

Permalink
feat: show more info in share modal about accesses
Browse files Browse the repository at this point in the history
  • Loading branch information
pyphilia committed Jul 28, 2021
1 parent fdf0092 commit 8ba07b1
Show file tree
Hide file tree
Showing 12 changed files with 163 additions and 54 deletions.
1 change: 1 addition & 0 deletions src/components/common/FavoriteButton.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import IconButton from '@material-ui/core/IconButton';
import { Map } from 'immutable';
import FavoriteIcon from '@material-ui/icons/Favorite';
import FavoriteBorderIcon from '@material-ui/icons/FavoriteBorder';
import { useTranslation } from 'react-i18next';
Expand Down
39 changes: 23 additions & 16 deletions src/components/common/SettingsHeader.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,24 +62,38 @@ const SettingsHeader = () => {
};

const renderMenu = () => {
if (!user || user.isEmpty()) {
const isSignedOut = !user || user.isEmpty();

if (isSignedOut) {
return (
<MenuItem
component="a"
href={`${AUTHENTICATION_HOST}/${API_ROUTES.buildSignInPath()}`}
<Menu
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
>
{t('Sign In')}
</MenuItem>
<MenuItem
component="a"
href={`${AUTHENTICATION_HOST}/${API_ROUTES.buildSignInPath()}`}
>
{t('Sign In')}
</MenuItem>
</Menu>
);
}

return (
<>
<Menu
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
>
<MenuItem onClick={goToProfile}>{t('Profile')}</MenuItem>
<MenuItem onClick={handleSignOut} id={USER_MENU_SIGN_OUT_OPTION_ID}>
{t('Sign Out')}
</MenuItem>
</>
</Menu>
);
};

Expand Down Expand Up @@ -107,14 +121,7 @@ const SettingsHeader = () => {
</Typography>
)}
</Box>
<Menu
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
>
{renderMenu()}
</Menu>
{renderMenu()}
</>
);
};
Expand Down
87 changes: 87 additions & 0 deletions src/components/context/AccessIndication.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import { Loader } from '@graasp/ui';
import Tooltip from '@material-ui/core/Tooltip';
import { Typography, Grid } from '@material-ui/core';
import IconButton from '@material-ui/core/IconButton';
import { useTranslation } from 'react-i18next';
import InfoIcon from '@material-ui/icons/Info';
import { LayoutContext } from './LayoutContext';
import { hooks } from '../../config/queryClient';
import ItemMemberships from '../item/ItemMemberships';
import { hasItemLoginEnabled, isItemPublic } from '../../utils/itemTag';
import { SHARE_MODAL_AVATAR_GROUP_MAX_AVATAR } from '../../config/constants';

const AccessIndication = ({ itemId, onClick }) => {
const { t } = useTranslation();

const { data: tags, isLoading: isTagsLoading } = hooks.useTags();
const { data: itemTags, isLoading: isItemTagsLoading } = hooks.useItemTags(
itemId,
);
const { setIsItemSettingsOpen } = useContext(LayoutContext);

const isPublic = isItemPublic({ itemTags, tags });
const hasItemLogin = hasItemLoginEnabled({ itemTags, tags });

const openSettings = () => {
onClick();
setIsItemSettingsOpen(true);
};

if (isTagsLoading || isItemTagsLoading) {
return <Loader />;
}

// check tags and display access methods' indications
let accessText = null;
let tooltipText = null;
if (isPublic) {
accessText = t('Public');
tooltipText = t('This item is public. Anyone can access this item.');
} else if (hasItemLogin) {
accessText = t('Anyone authenticated with the link');
tooltipText = t(
'This item enables item login. New users can access this item when they authenticate using the item login.',
);
}

if (accessText && tooltipText) {
return (
<Grid container justify="space-between" alignItems="center">
<Grid item>
<Typography variant="body1">
{`${t('Access')}: ${accessText}`}
</Typography>
</Grid>
<Grid item>
<Tooltip title={tooltipText}>
<IconButton
aria-label="access information"
color="primary"
onClick={openSettings}
>
<InfoIcon />
</IconButton>
</Tooltip>
</Grid>
</Grid>
);
}

// show memberships when the item is not public and does not allow iten login
return (
<ItemMemberships
onClick={openSettings}
id={itemId}
maxAvatar={SHARE_MODAL_AVATAR_GROUP_MAX_AVATAR}
/>
);
};

AccessIndication.propTypes = {
onClick: PropTypes.func.isRequired,
itemId: PropTypes.string.isRequired,
};

export default AccessIndication;
18 changes: 3 additions & 15 deletions src/components/context/ShareItemModalContext.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useContext, useEffect, useState } from 'react';
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import Button from '@material-ui/core/Button';
import IconButton from '@material-ui/core/IconButton';
Expand All @@ -14,7 +14,6 @@ import Link from '@material-ui/core/Link';
import MenuItem from '@material-ui/core/MenuItem';
import Tooltip from '@material-ui/core/Tooltip';
import FileCopyIcon from '@material-ui/icons/FileCopy';
import ItemMemberships from '../item/ItemMemberships';
import {
COMPOSE_VIEW_SELECTION,
PERFORM_VIEW_SELECTION,
Expand All @@ -23,16 +22,15 @@ import {
SHARE_LINK_CONTAINER_BORDER_STYLE,
SHARE_LINK_CONTAINER_BORDER_WIDTH,
SHARE_LINK_WIDTH,
SHARE_MODAL_AVATAR_GROUP_MAX_AVATAR,
} from '../../config/constants';
import { LayoutContext } from './LayoutContext';
import {
buildGraaspComposeView,
buildGraaspPerformView,
} from '../../config/paths';
import { copyToClipboard } from '../../utils/clipboard';
import notifier from '../../middlewares/notifier';
import { COPY_ITEM_LINK_TO_CLIPBOARD } from '../../types/clipboard';
import AccessIndication from './AccessIndication';

const ShareItemModalContext = React.createContext();

Expand Down Expand Up @@ -85,7 +83,6 @@ const useStyles = makeStyles((theme) => ({
const ShareItemModalProvider = ({ children }) => {
const { t } = useTranslation();
const classes = useStyles();
const { setIsItemSettingsOpen } = useContext(LayoutContext);

const [open, setOpen] = useState(false);
const [itemId, setItemId] = useState(null);
Expand Down Expand Up @@ -121,11 +118,6 @@ const ShareItemModalProvider = ({ children }) => {
setItemId(null);
};

const onClickMemberships = () => {
onClose();
setIsItemSettingsOpen(true);
};

const handleCopy = () => {
copyToClipboard(link, {
onSuccess: () => {
Expand Down Expand Up @@ -175,11 +167,7 @@ const ShareItemModalProvider = ({ children }) => {
</Tooltip>
</div>
</div>
<ItemMemberships
onClick={onClickMemberships}
id={itemId}
maxAvatar={SHARE_MODAL_AVATAR_GROUP_MAX_AVATAR}
/>
<AccessIndication itemId={itemId} onClick={onClose} />
</>
) : (
<Typography>{t('Item not specified')}</Typography>
Expand Down
14 changes: 2 additions & 12 deletions src/components/item/ItemMemberships.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React from 'react';
import { Loader } from '@graasp/ui';
import { useTranslation } from 'react-i18next';
import PropTypes from 'prop-types';
import Typography from '@material-ui/core/Typography';
Expand All @@ -17,22 +16,13 @@ import { membershipsWithoutUser } from '../../utils/membership';

const ItemMemberships = ({ id, maxAvatar, onClick }) => {
const { t } = useTranslation();
const { data: memberships, isLoading, isError } = hooks.useItemMemberships(
id,
);
const {
data: currentUser,
isLoading: isLoadingCurrentMember,
} = hooks.useCurrentMember();
const { data: memberships, isError } = hooks.useItemMemberships(id);
const { data: currentUser } = hooks.useCurrentMember();

if (!id) {
return null;
}

if (isLoading || isLoadingCurrentMember) {
return <Loader />;
}

if (!memberships || memberships.isEmpty() || isError) {
return null;
}
Expand Down
3 changes: 2 additions & 1 deletion src/components/item/header/ItemHeaderActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,12 @@ const ItemHeaderActions = ({ onClick, item }) => {

ItemHeaderActions.propTypes = {
onClick: PropTypes.func,
item: PropTypes.instanceOf(Map).isRequired,
item: PropTypes.instanceOf(Map),
};

ItemHeaderActions.defaultProps = {
onClick: () => {},
item: Map(),
};

export default ItemHeaderActions;
28 changes: 26 additions & 2 deletions src/components/item/settings/PublicSetting.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import { Map } from 'immutable';
import PropTypes from 'prop-types';
import Switch from '@material-ui/core/Switch';
import { useParams } from 'react-router';
import InfoIcon from '@material-ui/icons/Info';
import { MUTATION_KEYS } from '@graasp/query-client';
import { FormControlLabel } from '@material-ui/core';
import { FormControlLabel, IconButton, Tooltip } from '@material-ui/core';
import { useMutation, hooks } from '../../../config/queryClient';
import { getItemPublicTagFromItem } from '../../../utils/itemExtra';
import Loader from '../../common/Loader';
Expand Down Expand Up @@ -77,7 +78,30 @@ const PublicSwitch = ({ item }) => {
/>
);

return <FormControlLabel control={control} label={t('Item is public')} />;
const label = (
<>
{t('Item is public')}
{/* display more information if switch is disabled */}
{isSwitchDisabled && (
<Tooltip
title={t(
'This item is public because its parent is public. To set this item as private, you need to set its parent as private.',
)}
placement="right"
>
<IconButton fontSize="small" aria-label="access information">
<InfoIcon />
</IconButton>
</Tooltip>
)}
</>
);

return (
<>
<FormControlLabel control={control} label={label} />
</>
);
};

PublicSwitch.propTypes = {
Expand Down
3 changes: 1 addition & 2 deletions src/components/main/ItemsTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import DraggableTableRow from '../common/DraggableTableRow';
import DroppableTableBody from '../common/DroppableTableBody';
import EditButton from '../common/EditButton';
import ShareButton from '../common/ShareButton';
import { ItemSearchInput } from '../item/ItemSearch';
import ItemIcon from './ItemIcon';
import ItemMenu from './ItemMenu';
import TableHead from './TableHead';
Expand Down Expand Up @@ -393,7 +392,7 @@ ItemsTable.propTypes = {
tableTitle: PropTypes.string.isRequired,
id: PropTypes.string,
itemSearch: PropTypes.shape({
input: PropTypes.instanceOf(ItemSearchInput),
input: PropTypes.element,
}),
};

Expand Down
3 changes: 1 addition & 2 deletions src/components/main/TableToolbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import React from 'react';
import { useTranslation } from 'react-i18next';
import { ITEMS_TABLE_DELETE_SELECTED_ITEMS_ID } from '../../config/selectors';
import DeleteButton from '../common/DeleteButton';
import { ItemSearchInput } from '../item/ItemSearch';
import NewItemButton from './NewItemButton';

const useToolbarStyles = makeStyles((theme) => ({
Expand Down Expand Up @@ -76,7 +75,7 @@ TableToolbar.propTypes = {
numSelected: PropTypes.number.isRequired,
tableTitle: PropTypes.string,
selected: PropTypes.arrayOf(PropTypes.shape({}).isRequired).isRequired,
itemSearchInput: PropTypes.instanceOf(ItemSearchInput),
itemSearchInput: PropTypes.element,
};

TableToolbar.defaultProps = {
Expand Down
7 changes: 6 additions & 1 deletion src/langs/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,11 @@
"Perform": "Perform",
"Copy to Clipboard": "Copy to Clipboard",
"Item not specified": "Item not specified",
"You are being redirected…": "You are being redirected…"
"You are being redirected…": "You are being redirected…",
"Public": "Public",
"This item is public. Anyone can access this item.": "This item is public. Anyone can access this item.",
"Anyone authenticated with the link": "Anyone authenticated with the link",
"This item enables item login. New users can access this item when they authenticate using the item login.": "This item enables item login. New users can access this item when they authenticate using the item login.",
"Access": "Access"
}
}
6 changes: 6 additions & 0 deletions src/utils/itemTag.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,11 @@ import { SETTINGS } from '../config/constants';
export const getItemLoginTag = (tags) =>
tags?.find(({ name }) => name === SETTINGS.ITEM_LOGIN.name);

export const hasItemLoginEnabled = ({ tags, itemTags }) =>
Boolean(itemTags?.find(({ tagId }) => tagId === getItemLoginTag(tags)?.id));

export const getItemPublicTag = (tags) =>
tags?.find(({ name }) => name === SETTINGS.ITEM_PUBLIC.name);

export const isItemPublic = ({ tags, itemTags }) =>
Boolean(itemTags?.find(({ tagId }) => tagId === getItemPublicTag(tags)?.id));
8 changes: 5 additions & 3 deletions src/utils/membership.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ export const isSettingsEditionAllowedForUser = ({ memberships, memberId }) =>
);

export const isItemUpdateAllowedForUser = ({ memberships, memberId }) =>
memberships?.find(
({ memberId: mId, permission }) =>
mId === memberId && PERMISSIONS_EDITION_ALLOWED.includes(permission),
Boolean(
memberships?.find(
({ memberId: mId, permission }) =>
mId === memberId && PERMISSIONS_EDITION_ALLOWED.includes(permission),
),
);

export const membershipsWithoutUser = (memberships, userId) =>
Expand Down

0 comments on commit 8ba07b1

Please sign in to comment.