diff --git a/invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/landing_page/RecordManagement.js b/invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/landing_page/RecordManagement.js
index ca8fae0dd2..aeb2d38b66 100644
--- a/invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/landing_page/RecordManagement.js
+++ b/invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/landing_page/RecordManagement.js
@@ -11,7 +11,7 @@ import React, { Component } from "react";
import { Button, Grid, Icon, Message } from "semantic-ui-react";
import { EditButton } from "./EditButton";
-import { ShareButton } from "./ShareButton";
+import { ShareButton } from "./ShareOptions/ShareButton";
import { NewVersionButton } from "@js/invenio_rdm_records/";
import PropTypes from "prop-types";
import Overridable from "react-overridable";
diff --git a/invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/landing_page/ShareModal.js b/invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/landing_page/ShareModal.js
deleted file mode 100644
index c46aa3f3a3..0000000000
--- a/invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/landing_page/ShareModal.js
+++ /dev/null
@@ -1,262 +0,0 @@
-// This file is part of InvenioRDM
-// Copyright (C) 2021 Northwestern University.
-//
-// Invenio RDM Records is free software; you can redistribute it and/or modify it
-// under the terms of the MIT License; see LICENSE file for more details.
-
-import React, { useEffect, useState } from "react";
-import { Dropdown, Icon, Input, Button, Modal, Popup } from "semantic-ui-react";
-import axios from "axios";
-import { Trans } from "react-i18next";
-import { i18next } from "@translations/invenio_app_rdm/i18next";
-import { http } from "react-invenio-forms";
-import PropTypes from "prop-types";
-
-export const ShareModal = ({ recid, open, handleClose }) => {
- const [accessLinkObj, setAccessLinkObj] = useState();
- const [linkCreated, setLinkCreated] = useState(false);
- const [shareMode, setShareMode] = useState("view");
- const [copied, setCopied] = useState(false);
-
- const dropdownOptions = [
- { key: "view", text: i18next.t("Can view"), value: "view" },
- { key: "preview", text: i18next.t("Can preview"), value: "preview" },
- { key: "edit", text: i18next.t("Can edit"), value: "edit" },
- ];
-
- const message = {
- view: (
-
-
- Anyone with this link can view all versions of this record &
- files.
-
-
- ),
- preview: (
-
-
- Anyone with this link{" "}
- can view all published and unpublished versions of this
- record & files.
-
-
- ),
- edit: (
-
-
- Anyone with an account and this link can edit all versions of
- this record & files.
-
-
- ),
- };
-
- const copyButtonRef = React.createRef();
-
- const getAccessLink = (linkObj) => {
- const extraParam = shareMode === "preview" ? "preview=1&" : "";
- return linkObj ? `${window.location}?${extraParam}token=${linkObj.token}` : "";
- };
-
- const createAccessLink = async () => {
- await http
- .post(
- `/api/records/${recid}/access/links`,
- { permission: shareMode },
- {
- headers: {
- "Accept": "application/json",
- "Content-Type": "application/json",
- },
- withCredentials: true,
- }
- )
- .then((resp) => {
- setAccessLinkObj(resp.data);
- setLinkCreated(true);
- });
- };
-
- const copyAccessLink = () => {
- const copyText = document.querySelector("#input");
- copyText.select();
- let copySuccess = document.execCommand("copy");
- setCopied(copySuccess);
- };
-
- const handleChangeMode = (e, { value }) => setShareMode(value);
-
- const handleDelete = async () => {
- await http
- .delete(`/api/records/${recid}/access/links/${accessLinkObj.id}`, {
- headers: {
- Accept: "application/json",
- },
- withCredentials: true,
- })
- .then(() => {
- setAccessLinkObj();
- setLinkCreated(false);
- });
- };
-
- useEffect(() => {
- linkCreated && copyAccessLink();
- }, [linkCreated]);
-
- useEffect(() => {
- // Update access link according to share mode
- let source = axios.CancelToken.source();
-
- if (accessLinkObj) {
- (async () => {
- await http.patch(
- `/api/records/${recid}/access/links/${accessLinkObj.id}`,
- { permission: shareMode },
- {
- headers: {
- "Content-Type": "application/json",
- },
- withCredentials: true,
- cancelToken: source.token,
- }
- );
- })();
- }
- return () => {
- source.cancel();
- };
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [shareMode]);
-
- useEffect(() => {
- if (accessLinkObj) {
- setShareMode(accessLinkObj.permission);
- }
- }, [accessLinkObj]);
-
- useEffect(() => {
- // Fetch existing access link
- let source = axios.CancelToken.source();
-
- (async () => {
- const result = await axios(`/api/records/${recid}/access/links`, {
- headers: {
- Accept: "application/json",
- },
- withCredentials: true,
- cancelToken: source.token,
- });
- const { hits, total } = result.data.hits;
- if (total > 0) {
- // Only accessing first access link for MVP.
- setAccessLinkObj(hits[0]);
- }
- })();
-
- return () => {
- source.cancel();
- };
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
-
- useEffect(() => {
- copyButtonRef.current?.focus(); // Accessiblity: focus the copy-button when modal opens
- }, [copyButtonRef]);
-
- useEffect(() => {
- let popupTimeout = setTimeout(() => {
- setCopied(false);
- }, 1500);
-
- return () => {
- clearTimeout(popupTimeout);
- };
- }, [copied]);
-
- return (
-
-
-
-
-
-
-
-
-
- {accessLinkObj ? i18next.t("Copy link") : i18next.t("Get a link")}
-
- }
- />
-
-
-
{copied && i18next.t("Copied")}
-
-
-
-
-
- {!accessLinkObj
- ? i18next.t(
- "No link has been created. Click on 'Get a Link' to make a new link."
- )
- : message[shareMode]}
-
-
-
-
-
- {!!accessLinkObj && (
-
- )}
-
-
-
- );
-};
-
-ShareModal.propTypes = {
- recid: PropTypes.string.isRequired,
- open: PropTypes.bool.isRequired,
- handleClose: PropTypes.func.isRequired,
-};
diff --git a/invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/landing_page/ShareOptions/GrantsTab.js b/invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/landing_page/ShareOptions/GrantsTab.js
new file mode 100644
index 0000000000..5bdad2d9fd
--- /dev/null
+++ b/invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/landing_page/ShareOptions/GrantsTab.js
@@ -0,0 +1,101 @@
+// This file is part of InvenioRDM
+// Copyright (C) 2021 Northwestern University.
+// Copyright (C) 2023 TU Wien.
+//
+// Invenio RDM Records is free software; you can redistribute it and/or modify it
+// under the terms of the MIT License; see LICENSE file for more details.
+
+import React, { useEffect, useState } from "react";
+import { Dropdown, Icon, List, Button, Modal, Tab } from "semantic-ui-react";
+import { i18next } from "@translations/invenio_app_rdm/i18next";
+import { http } from "react-invenio-forms";
+import PropTypes from "prop-types";
+
+export const GrantsTab = ({ recid, dropdownOptions }) => {
+ const [accessGrants, setAccessGrants] = useState({});
+
+ useEffect(async () => {
+ try {
+ const result = await http.get(`/api/records/${recid}/access/grants?expand=1`);
+ const { hits } = result.data.hits;
+ setAccessGrants(hits);
+ } catch (err) {
+ console.log(err);
+ }
+ }, []);
+
+ const grantIconDict = {
+ edit: "edit",
+ view: "eye",
+ preview: "file alternate",
+ };
+
+ const handleGrantModeChange = (id, permission) => {
+ http
+ .patch(`/api/records/${recid}/access/grants/${id}?expand=1`, {
+ permission: permission,
+ })
+ .then((result) => {
+ let newAccessGrants = { ...accessGrants };
+ delete newAccessGrants[id];
+ newAccessGrants[result.data.id ?? id] = result.data;
+ setAccessGrants(newAccessGrants);
+ });
+ };
+
+ const handleDeleteGrant = (id) => {
+ http.delete(`/api/records/${recid}/access/grants/${id}`).then((_result) => {
+ const newAccessGrants = [...accessGrants];
+ delete newAccessGrants[id];
+ setAccessGrants(newAccessGrants);
+ });
+ };
+
+ return (
+ <>
+
Access shared with users and groups
+
+
+ {Object.keys(accessGrants).map((grantId) => (
+
+
+
+ {accessGrants[grantId].expanded.subject.profile?.full_name ??
+ `Unknown user (#${accessGrants[grantId].expanded.subject.id})`}{" "}
+ {accessGrants[grantId].origin.startsWith("request:")
+ ? "(created via request)"
+ : "(created manually)"}
+ {
+ handleGrantModeChange(grantId, value);
+ }}
+ />
+
+
+
+ ))}
+
+ >
+ );
+};
+
+GrantsTab.propTypes = {
+ recid: PropTypes.string.isRequired,
+ dropdownOptions: PropTypes.array.isRequired,
+};
diff --git a/invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/landing_page/ShareOptions/LinksTab.js b/invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/landing_page/ShareOptions/LinksTab.js
new file mode 100644
index 0000000000..26547ca66a
--- /dev/null
+++ b/invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/landing_page/ShareOptions/LinksTab.js
@@ -0,0 +1,233 @@
+// This file is part of InvenioRDM
+// Copyright (C) 2021 Northwestern University.
+//
+// Invenio RDM Records is free software; you can redistribute it and/or modify it
+// under the terms of the MIT License; see LICENSE file for more details.
+
+import React, { useEffect, useState } from "react";
+import { Dropdown, Icon, Input, Button, Modal, Popup, Header } from "semantic-ui-react";
+import axios from "axios";
+import { Trans } from "react-i18next";
+import { i18next } from "@translations/invenio_app_rdm/i18next";
+import { http } from "react-invenio-forms";
+import PropTypes from "prop-types";
+import { resolveObjectURL } from "buffer";
+
+export const LinksTab = ({ recid, dropdownOptions }) => {
+ const [accessLinks, setAccessLinks] = useState({});
+ const [linkCreated, setLinkCreated] = useState(false);
+ const [shareMode, setShareMode] = useState("view");
+ const [copied, setCopied] = useState(false);
+
+ const message = {
+ view: (
+
+
+ Anyone with this link can view all versions of this record &
+ files.
+
+
+ ),
+ preview: (
+
+
+ Anyone with this link{" "}
+ can view all published and unpublished versions of this
+ record & files.
+
+
+ ),
+ edit: (
+
+
+ Anyone with an account and this link can edit all versions of
+ this record & files.
+
+
+ ),
+ };
+
+ const copyButtonRef = React.createRef();
+
+ const getAccessLink = (linkId) => {
+ const linkObj = accessLinks[linkId];
+ const extraParam = linkObj?.permission === "preview" ? "preview=1&" : "";
+ return linkObj ? `${window.location}?${extraParam}token=${linkObj.token}` : "";
+ };
+
+ const createAccessLink = async () => {
+ await http
+ .post(
+ `/api/records/${recid}/access/links`,
+ { permission: shareMode },
+ {
+ headers: {
+ "Accept": "application/json",
+ "Content-Type": "application/json",
+ },
+ withCredentials: true,
+ }
+ )
+ .then((resp) => {
+ setAccessLinks({[resp.data.id]: resp.data});
+ setLinkCreated(true);
+ });
+ };
+
+ const copyAccessLink = (linkId) => {
+ const copyText = document.querySelector("#input");
+ copyText.select();
+ let copySuccess = document.execCommand("copy");
+ setCopied(copySuccess);
+ };
+
+ const handleChangeMode = (linkId, value) => {
+ setShareMode(value);
+ }
+
+ const handleDelete = (linkId) => {
+ http.delete(`/api/records/${recid}/access/links/${linkId}`)
+ .then(() => {
+ let newLinks = {...accessLinks};
+ delete newLinks[linkId];
+ setAccessLinks(newLinks);
+ setLinkCreated(false);
+ });
+ };
+
+ useEffect(() => {
+ linkCreated && copyAccessLink();
+ }, [linkCreated]);
+
+ /*
+ useEffect(() => {
+ // Update access link according to share mode
+ let source = axios.CancelToken.source();
+
+ if (accessLinkObj) {
+ (async () => {
+ await http.patch(
+ `/api/records/${recid}/access/links/${accessLinkObj.id}`,
+ { permission: shareMode },
+ { cancelToken: source.token }
+ );
+ })();
+ }
+ return () => {
+ source.cancel();
+ };
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [shareMode]);
+ */
+
+ /*
+ useEffect(() => {
+ if (accessLinkObj) {
+ setShareMode(accessLinkObj.permission);
+ }
+ }, [accessLinkObj]);
+ */
+
+ useEffect(() => {
+ // Fetch existing access link
+ let source = axios.CancelToken.source();
+
+ (async () => {
+ const result = await axios(`/api/records/${recid}/access/links`, {
+ cancelToken: source.token,
+ });
+
+ let newAccessLinks = {};
+ result.data.hits.hits.forEach(hit => {
+ newAccessLinks[hit.id] = hit;
+ });
+ setAccessLinks(newAccessLinks);
+ })();
+
+ return () => {
+ source.cancel();
+ };
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ useEffect(() => {
+ copyButtonRef.current?.focus(); // Accessiblity: focus the copy-button when modal opens
+ }, [copyButtonRef]);
+
+ useEffect(() => {
+ let popupTimeout = setTimeout(() => {
+ setCopied(false);
+ }, 1500);
+
+ return () => {
+ clearTimeout(popupTimeout);
+ };
+ }, [copied]);
+
+ const linkIds = Object.keys(accessLinks)
+ const firstLink = linkIds.length > 0 ? accessLinks[linkIds[0]] : null;
+
+ return (
+ <>
+
+
+
+
+
+
+ {linkIds.length <= 0
+ ? i18next.t(
+ "No link has been created. Click on 'Get a Link' to make a new link."
+ )
+ : message[shareMode]}
+
+ >
+ );
+};
+
+LinksTab.propTypes = {
+ recid: PropTypes.string.isRequired,
+ dropdownOptions: PropTypes.array.isRequired,
+};
diff --git a/invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/landing_page/ShareButton.js b/invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/landing_page/ShareOptions/ShareButton.js
similarity index 98%
rename from invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/landing_page/ShareButton.js
rename to invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/landing_page/ShareOptions/ShareButton.js
index 10e6baf389..a3de5795f1 100644
--- a/invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/landing_page/ShareButton.js
+++ b/invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/landing_page/ShareOptions/ShareButton.js
@@ -2,6 +2,7 @@
// Copyright (C) 2021 CERN.
// Copyright (C) 2021 Northwestern University.
// Copyright (C) 2021 Graz University of Technology.
+// Copyright (C) 2023 TU Wien.
//
// Invenio RDM Records is free software; you can redistribute it and/or modify it
// under the terms of the MIT License; see LICENSE file for more details.
diff --git a/invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/landing_page/ShareOptions/ShareModal.js b/invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/landing_page/ShareOptions/ShareModal.js
new file mode 100644
index 0000000000..255b181512
--- /dev/null
+++ b/invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/landing_page/ShareOptions/ShareModal.js
@@ -0,0 +1,85 @@
+// This file is part of InvenioRDM
+// Copyright (C) 2021 Northwestern University.
+// Copyright (C) 2023 TU Wien.
+//
+// Invenio RDM Records is free software; you can redistribute it and/or modify it
+// under the terms of the MIT License; see LICENSE file for more details.
+
+import React from "react";
+import { Icon, Button, Modal, Tab } from "semantic-ui-react";
+import { i18next } from "@translations/invenio_app_rdm/i18next";
+import PropTypes from "prop-types";
+import { LinksTab } from "./LinksTab";
+import { GrantsTab } from "./GrantsTab";
+
+export const ShareModal = ({ recid, open, handleClose }) => {
+ const dropdownOptions = [
+ { key: "view", text: i18next.t("Can view"), value: "view" },
+ { key: "preview", text: i18next.t("Can preview"), value: "preview" },
+ { key: "edit", text: i18next.t("Can edit"), value: "edit" },
+ ];
+
+ const panes = [
+ {
+ menuItem: { icon: "users", content: "People and groups" },
+ pane: (
+
+
+
+ ),
+ },
+ {
+ menuItem: { icon: "linkify", content: "Links" },
+ pane: (
+
+
+
+ ),
+ },
+ {
+ menuItem: { icon: "cogs", content: "Access requests" },
+ pane: (
+
+ Coming soon
+
+ ),
+ },
+ ];
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+ShareModal.propTypes = {
+ recid: PropTypes.string.isRequired,
+ open: PropTypes.bool.isRequired,
+ handleClose: PropTypes.func.isRequired,
+};