diff --git a/cypress/e2e/item/share/changeVisibility.cy.ts b/cypress/e2e/item/share/changeVisibility.cy.ts
index 72957ff24..57a891919 100644
--- a/cypress/e2e/item/share/changeVisibility.cy.ts
+++ b/cypress/e2e/item/share/changeVisibility.cy.ts
@@ -2,6 +2,7 @@ import {
ItemLoginSchemaType,
ItemTagType,
PackedFolderItemFactory,
+ PublicationStatus,
} from '@graasp/sdk';
import { buildItemPath } from '@/config/paths';
@@ -10,8 +11,11 @@ import { SETTINGS } from '../../../../src/config/constants';
import {
SHARE_ITEM_PSEUDONYMIZED_SCHEMA_ID,
SHARE_ITEM_VISIBILITY_SELECT_ID,
+ UPDATE_VISIBILITY_MODAL_VALIDATE_BUTTON,
+ buildDataCyWrapper,
buildShareButtonId,
} from '../../../../src/config/selectors';
+import { PublishedItemFactory } from '../../../fixtures/items';
const changeVisibility = (value: string): void => {
cy.get(`#${SHARE_ITEM_VISIBILITY_SELECT_ID}`).click();
@@ -25,12 +29,12 @@ describe('Visibility of an Item', () => {
cy.visit(buildItemPath(item.id));
cy.get(`#${buildShareButtonId(item.id)}`).click();
- const visiblitySelect = cy.get(
+ const visibilitySelect = cy.get(
`#${SHARE_ITEM_VISIBILITY_SELECT_ID} + input`,
);
// visibility select default value
- visiblitySelect.should('have.value', SETTINGS.ITEM_PRIVATE.name);
+ visibilitySelect.should('have.value', SETTINGS.ITEM_PRIVATE.name);
// change private -> public
changeVisibility(SETTINGS.ITEM_PUBLIC.name);
@@ -47,12 +51,12 @@ describe('Visibility of an Item', () => {
cy.visit(buildItemPath(item.id));
cy.get(`#${buildShareButtonId(item.id)}`).click();
cy.wait(1000);
- const visiblitySelect = cy.get(
+ const visibilitySelect = cy.get(
`#${SHARE_ITEM_VISIBILITY_SELECT_ID} + input`,
);
// visibility select default value
- visiblitySelect.should('have.value', SETTINGS.ITEM_PUBLIC.name);
+ visibilitySelect.should('have.value', SETTINGS.ITEM_PUBLIC.name);
// change public -> private
changeVisibility(SETTINGS.ITEM_PRIVATE.name);
@@ -69,12 +73,12 @@ describe('Visibility of an Item', () => {
cy.visit(buildItemPath(item.id));
cy.get(`#${buildShareButtonId(item.id)}`).click();
cy.wait(1000);
- const visiblitySelect = cy.get(
+ const visibilitySelect = cy.get(
`#${SHARE_ITEM_VISIBILITY_SELECT_ID} + input`,
);
// visibility select default value
- visiblitySelect.should('have.value', SETTINGS.ITEM_PUBLIC.name);
+ visibilitySelect.should('have.value', SETTINGS.ITEM_PUBLIC.name);
// change public -> item login
changeVisibility(SETTINGS.ITEM_LOGIN.name);
@@ -125,4 +129,71 @@ describe('Visibility of an Item', () => {
expect(url).to.include(item.id);
});
});
+
+ describe('Change visibility of published item', () => {
+ it('User should validate the change to private', () => {
+ const item = PublishedItemFactory(
+ PackedFolderItemFactory({}, { publicTag: {} }),
+ );
+ cy.setUpApi({
+ items: [item],
+ itemPublicationStatus: PublicationStatus.Published,
+ });
+ cy.visit(buildItemPath(item.id));
+ cy.get(`#${buildShareButtonId(item.id)}`).click();
+ const visibilitySelect = cy.get(
+ `#${SHARE_ITEM_VISIBILITY_SELECT_ID} + input`,
+ );
+
+ // visibility select default value
+ visibilitySelect.should('have.value', SETTINGS.ITEM_PUBLIC.name);
+
+ // try to change public -> private
+ changeVisibility(SETTINGS.ITEM_PRIVATE.name);
+ // the user have to confirm that changing visibility will remove the publication
+ cy.get(
+ `${buildDataCyWrapper(UPDATE_VISIBILITY_MODAL_VALIDATE_BUTTON)}`,
+ ).click();
+ cy.wait(`@deleteItemTag-${ItemTagType.Public}`).then(
+ ({ request: { url } }) => {
+ expect(url).to.contain(item.id);
+ },
+ );
+ });
+
+ it('User should validate the change to item login', () => {
+ const item = PublishedItemFactory(
+ PackedFolderItemFactory({}, { publicTag: {} }),
+ );
+ cy.setUpApi({
+ items: [item],
+ itemPublicationStatus: PublicationStatus.Published,
+ });
+ cy.visit(buildItemPath(item.id));
+ cy.get(`#${buildShareButtonId(item.id)}`).click();
+ const visibilitySelect = cy.get(
+ `#${SHARE_ITEM_VISIBILITY_SELECT_ID} + input`,
+ );
+
+ // visibility select default value
+ visibilitySelect.should('have.value', SETTINGS.ITEM_PUBLIC.name);
+
+ // try to change public -> item login
+ changeVisibility(SETTINGS.ITEM_LOGIN.name);
+ // the user have to confirm that changing visibility will remove the publication
+ cy.get(
+ `${buildDataCyWrapper(UPDATE_VISIBILITY_MODAL_VALIDATE_BUTTON)}`,
+ ).click();
+ cy.wait([
+ `@deleteItemTag-${ItemTagType.Public}`,
+ '@putItemLoginSchema',
+ ]).then((data) => {
+ const {
+ request: { url },
+ } = data[0];
+ expect(url).to.contain(item.id);
+ expect(url).to.contain(ItemTagType.Public); // originally item login
+ });
+ });
+ });
});
diff --git a/src/components/item/sharing/UpdateVisibilityModal.tsx b/src/components/item/sharing/UpdateVisibilityModal.tsx
new file mode 100644
index 000000000..a19368cd3
--- /dev/null
+++ b/src/components/item/sharing/UpdateVisibilityModal.tsx
@@ -0,0 +1,79 @@
+import {
+ Button,
+ Dialog,
+ DialogActions,
+ DialogContent,
+ DialogTitle,
+ Typography,
+} from '@mui/material';
+
+import { useBuilderTranslation } from '@/config/i18n';
+import {
+ UPDATE_VISIBILITY_MODAL_CANCEL_BUTTON,
+ UPDATE_VISIBILITY_MODAL_VALIDATE_BUTTON,
+} from '@/config/selectors';
+import { BUILDER } from '@/langs/constants';
+
+export type Visibility = {
+ name: string;
+ value: string;
+};
+
+type Props = {
+ isOpen: boolean;
+ newVisibility?: Visibility;
+ onClose: () => void;
+ onValidate: (visibility: string) => void;
+};
+
+export const UpdateVisibilityModal = ({
+ isOpen,
+ newVisibility,
+ onClose,
+ onValidate,
+}: Props): JSX.Element | null => {
+ const { t } = useBuilderTranslation();
+
+ if (!newVisibility) {
+ return null;
+ }
+
+ const handleValidate = async () => {
+ onValidate(newVisibility.value);
+ };
+
+ return (
+
+ );
+};
+
+export default UpdateVisibilityModal;
diff --git a/src/components/item/sharing/VisibilitySelect.hook.tsx b/src/components/item/sharing/VisibilitySelect.hook.tsx
new file mode 100644
index 000000000..d27db292c
--- /dev/null
+++ b/src/components/item/sharing/VisibilitySelect.hook.tsx
@@ -0,0 +1,82 @@
+import { useState } from 'react';
+
+import { PublicationStatus } from '@graasp/sdk';
+
+import { hooks } from '@/config/queryClient';
+
+import { SETTINGS } from '../../../config/constants';
+import { useBuilderTranslation } from '../../../config/i18n';
+import { BUILDER } from '../../../langs/constants';
+import { Visibility } from './UpdateVisibilityModal';
+
+const { usePublicationStatus } = hooks;
+
+type Props = {
+ itemId: string;
+ visibility?: string;
+ updateVisibility: (newVisibility: string) => void | Promise;
+};
+
+type UseVisibilitySelect = {
+ isModalOpen: boolean;
+ pendingVisibility: Visibility | undefined;
+ onCloseModal: () => void;
+ onValidateModal: (newVisibility: string) => void;
+ onVisibilityChange: (newVisibility: string) => void;
+};
+
+const useVisibilitySelect = ({
+ itemId,
+ visibility,
+ updateVisibility,
+}: Props): UseVisibilitySelect => {
+ const { t: translateBuilder } = useBuilderTranslation();
+ const { data: publicationStatus } = usePublicationStatus(itemId);
+
+ // The visibility value is temporary and awaits user confirmation through the dialog.
+ const [pendingVisibility, setPendingVisibility] = useState<
+ Visibility | undefined
+ >();
+
+ const translatedVisibilities = {
+ [SETTINGS.ITEM_LOGIN.name]: translateBuilder(
+ BUILDER.ITEM_SETTINGS_VISIBILITY_PSEUDONYMIZED_LABEL,
+ ),
+ [SETTINGS.ITEM_PUBLIC.name]: translateBuilder(
+ BUILDER.ITEM_SETTINGS_VISIBILITY_PUBLIC_INFORMATIONS,
+ ),
+ [SETTINGS.ITEM_PRIVATE.name]: translateBuilder(
+ BUILDER.ITEM_SETTINGS_VISIBILITY_PRIVATE_LABEL,
+ ),
+ };
+
+ const onVisibilityChange = (newVisibility: string) => {
+ if (
+ visibility === SETTINGS.ITEM_PUBLIC.name &&
+ publicationStatus === PublicationStatus.Published
+ ) {
+ setPendingVisibility({
+ name: translatedVisibilities[newVisibility],
+ value: newVisibility,
+ });
+ } else {
+ updateVisibility(newVisibility);
+ }
+ };
+
+ const onCloseModal = () => setPendingVisibility(undefined);
+ const onValidateModal = (newVisibility: string) => {
+ onCloseModal();
+ updateVisibility(newVisibility);
+ };
+
+ return {
+ isModalOpen: Boolean(pendingVisibility),
+ pendingVisibility,
+ onCloseModal,
+ onValidateModal,
+ onVisibilityChange,
+ };
+};
+
+export default useVisibilitySelect;
diff --git a/src/components/item/sharing/VisibilitySelect.tsx b/src/components/item/sharing/VisibilitySelect.tsx
index c931f7a7b..fa796ee0b 100644
--- a/src/components/item/sharing/VisibilitySelect.tsx
+++ b/src/components/item/sharing/VisibilitySelect.tsx
@@ -10,6 +10,8 @@ import { useBuilderTranslation } from '../../../config/i18n';
import { SHARE_ITEM_VISIBILITY_SELECT_ID } from '../../../config/selectors';
import { BUILDER } from '../../../langs/constants';
import ItemLoginSchemaSelect from './ItemLoginSchemaSelect';
+import UpdateVisibilityModal from './UpdateVisibilityModal';
+import useVisibilitySelect from './VisibilitySelect.hook';
type Props = {
item: PackedItem;
@@ -28,6 +30,18 @@ const VisibilitySelect = ({ item, edit }: Props): JSX.Element | null => {
updateVisibility,
} = useVisibility(item);
+ const {
+ isModalOpen,
+ pendingVisibility,
+ onCloseModal,
+ onValidateModal,
+ onVisibilityChange,
+ } = useVisibilitySelect({
+ itemId: item.id,
+ visibility,
+ updateVisibility,
+ });
+
if (isLoading) {
return ;
}
@@ -68,10 +82,18 @@ const VisibilitySelect = ({ item, edit }: Props): JSX.Element | null => {
return (
<>
+ {isModalOpen && (
+
+ )}
{edit && (