diff --git a/src/components/common/PublishButton.js b/src/components/common/PublishButton.js new file mode 100644 index 000000000..de1050e40 --- /dev/null +++ b/src/components/common/PublishButton.js @@ -0,0 +1,40 @@ +import React, { useContext } from 'react'; +import PropTypes from 'prop-types'; +import IconButton from '@material-ui/core/IconButton'; +import ExploreIcon from '@material-ui/icons/Explore'; +import { useTranslation } from 'react-i18next'; +import CloseIcon from '@material-ui/icons/Close'; +import Tooltip from '@material-ui/core/Tooltip'; +import { + buildPublishButtonId, + PUBLISH_ITEM_BUTTON_CLASS, +} from '../../config/selectors'; +import { LayoutContext } from '../context/LayoutContext'; + +const PublishButton = ({ itemId }) => { + const { t } = useTranslation(); + const { setIsItemPublishOpen, isItemPublishOpen } = useContext(LayoutContext); + + const onClick = () => { + setIsItemPublishOpen(!isItemPublishOpen); + }; + + return ( + + + {isItemPublishOpen ? : } + + + ); +}; + +PublishButton.propTypes = { + itemId: PropTypes.string.isRequired, +}; + +export default PublishButton; diff --git a/src/components/context/LayoutContext.js b/src/components/context/LayoutContext.js index 4b9a6888f..2c8afccdf 100644 --- a/src/components/context/LayoutContext.js +++ b/src/components/context/LayoutContext.js @@ -16,6 +16,7 @@ const LayoutContextProvider = ({ children }) => { // todo: separate in item specific context const [isItemSettingsOpen, setIsItemSettingsOpen] = useState(false); const [isItemSharingOpen, setIsItemSharingOpen] = useState(false); + const [isItemPublishOpen, setIsItemPublishOpen] = useState(false); const [isDashboardOpen, setIsDashboardOpen] = useState(false); const [isMainMenuOpen, setIsMainMenuOpen] = useState(true); @@ -41,6 +42,8 @@ const LayoutContextProvider = ({ children }) => { setIsItemSharingOpen, isDashboardOpen, setIsDashboardOpen, + isItemPublishOpen, + setIsItemPublishOpen, }), [ editingItemId, @@ -48,6 +51,7 @@ const LayoutContextProvider = ({ children }) => { isItemMetadataMenuOpen, isItemSettingsOpen, isItemSharingOpen, + isItemPublishOpen, isMainMenuOpen, isDashboardOpen, setIsDashboardOpen, diff --git a/src/components/item/header/ItemHeaderActions.js b/src/components/item/header/ItemHeaderActions.js index 00d5e2012..a8008a606 100644 --- a/src/components/item/header/ItemHeaderActions.js +++ b/src/components/item/header/ItemHeaderActions.js @@ -29,6 +29,7 @@ import { import { hooks } from '../../../config/queryClient'; import { CurrentUserContext } from '../../context/CurrentUserContext'; import AnalyticsDashboardButton from '../../common/AnalyticsDashboardButton'; +import PublishButton from '../../common/PublishButton'; const useStyles = makeStyles((theme) => ({ root: { @@ -87,6 +88,7 @@ const ItemHeaderActions = ({ onClickMetadata, onClickChatbox, item }) => { )} + diff --git a/src/components/item/publish/ItemPublishTab.js b/src/components/item/publish/ItemPublishTab.js new file mode 100644 index 000000000..9485092eb --- /dev/null +++ b/src/components/item/publish/ItemPublishTab.js @@ -0,0 +1,51 @@ +import React, { useContext, useEffect } from 'react'; +import Container from '@material-ui/core/Container'; +import PropTypes from 'prop-types'; +import { Map } from 'immutable'; +import { Loader } from '@graasp/ui'; +import { makeStyles } from '@material-ui/core'; +import { isItemUpdateAllowedForUser } from '../../../utils/membership'; +import { LayoutContext } from '../../context/LayoutContext'; +import { CurrentUserContext } from '../../context/CurrentUserContext'; +import ItemPublishConfiguration from '../sharing/ItemPublishConfiguration'; + +const useStyles = makeStyles((theme) => ({ + wrapper: { + marginTop: theme.spacing(2), + }, +})); + +const ItemPublishTab = ({ item, memberships }) => { + const classes = useStyles(); + const { data: currentMember, isLoadingCurrentMember } = + useContext(CurrentUserContext); + const { setIsItemPublishOpen } = useContext(LayoutContext); + + const canEdit = isItemUpdateAllowedForUser({ + memberships, + memberId: currentMember?.get('id'), + }); + + useEffect( + () => () => { + setIsItemPublishOpen(false); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ); + + if (isLoadingCurrentMember) { + return ; + } + + return ( + + + + ); +}; +ItemPublishTab.propTypes = { + item: PropTypes.instanceOf(Map).isRequired, + memberships: PropTypes.arrayOf(PropTypes.shape({})).isRequired, +}; +export default ItemPublishTab; diff --git a/src/components/item/sharing/ItemPublishConfiguration.js b/src/components/item/sharing/ItemPublishConfiguration.js index 5e6bcfb9c..55f1ceff2 100644 --- a/src/components/item/sharing/ItemPublishConfiguration.js +++ b/src/components/item/sharing/ItemPublishConfiguration.js @@ -1,5 +1,5 @@ import React, { useContext, useState, useEffect } from 'react'; -import PropTypes, { string } from 'prop-types'; +import PropTypes from 'prop-types'; import { useTranslation } from 'react-i18next'; import { useParams } from 'react-router'; import { Loader } from '@graasp/ui'; @@ -26,10 +26,16 @@ import { ITEM_VALIDATION_BUTTON_ID, } from '../../../config/selectors'; import { getValidationStatusFromItemValidations } from '../../../utils/itemValidation'; +import { + getTagByName, + getVisibilityTagAndItemTag, +} from '../../../utils/itemTag'; const { DELETE_ITEM_TAG, POST_ITEM_TAG, POST_ITEM_VALIDATION } = MUTATION_KEYS; const { buildItemValidationAndReviewsKey } = DATA_KEYS; const { + useTags, + useItemTags, useItemValidationAndReviews, useItemValidationStatuses, useItemValidationReviewStatuses, @@ -59,14 +65,7 @@ const useStyles = makeStyles((theme) => ({ }, })); -const ItemPublishConfiguration = ({ - item, - edit, - tagValue, - itemTagValue, - publishedTag, - publicTag, -}) => { +const ItemPublishConfiguration = ({ item, edit }) => { const { t } = useTranslation(); const classes = useStyles(); // current user @@ -74,8 +73,22 @@ const ItemPublishConfiguration = ({ // current item const { itemId } = useParams(); + // item tags const { mutate: deleteItemTag } = useMutation(DELETE_ITEM_TAG); const { mutate: postItemTag } = useMutation(POST_ITEM_TAG); + + const { data: tags, isLoading: isTagsLoading } = useTags(); + const { + data: itemTags, + isLoading: isItemTagsLoading, + isError, + } = useItemTags(itemId); + + const [itemTagValue, setItemTagValue] = useState(false); + const [tagValue, setTagValue] = useState(false); + const [isDisabled, setIsDisabled] = useState(false); + + // item validation const { mutate: validateItem } = useMutation(POST_ITEM_VALIDATION); // get map of item validation and review statuses @@ -101,6 +114,21 @@ const ItemPublishConfiguration = ({ const [itemValidationStatus, setItemValidationStatus] = useState(false); + // update state variables depending on fetch values + useEffect(() => { + if (tags && itemTags) { + const { tag, itemTag } = getVisibilityTagAndItemTag({ tags, itemTags }); + setItemTagValue(itemTag); + setTagValue(tag); + + // disable setting if any visiblity is set on any ancestor items + setIsDisabled( + tag && itemTag?.itemPath && itemTag?.itemPath !== item?.get('path'), + ); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [tags, itemTags, item]); + useEffect(() => { // process when we fetch the item validation and review records if (ivByStatus) { @@ -114,10 +142,14 @@ const ItemPublishConfiguration = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [ivByStatus]); - if (isLoading) { + if (isLoading || isTagsLoading || isItemTagsLoading) { return ; } + if (isError) { + return null; + } + const handleValidate = () => { // prevent re-send request if the item is already successfully validated, or a validation is already pending if ( @@ -132,6 +164,8 @@ const ItemPublishConfiguration = ({ }; const publishItem = () => { + const publishedTag = getTagByName(tags, SETTINGS.ITEM_PUBLISHED.name); + const publicTag = getTagByName(tags, SETTINGS.ITEM_PUBLIC.name); // post published tag postItemTag({ id: itemId, @@ -250,6 +284,10 @@ const ItemPublishConfiguration = ({ )}