diff --git a/cypress/e2e/invitations/createInvitation.cy.js b/cypress/e2e/invitations/createInvitation.cy.js index 6c1790318..3acc8ee8a 100644 --- a/cypress/e2e/invitations/createInvitation.cy.js +++ b/cypress/e2e/invitations/createInvitation.cy.js @@ -5,7 +5,7 @@ import { SHARE_ITEM_SHARE_BUTTON_ID, buildShareButtonId, } from '../../../src/config/selectors'; -import { PERMISSION_LEVELS } from '../../../src/enums'; +import { PERMISSION_LEVELS } from '../../fixtures/enum'; import { SAMPLE_ITEMS } from '../../fixtures/items'; import { MEMBERS } from '../../fixtures/members'; diff --git a/cypress/e2e/invitations/editInvitation.cy.js b/cypress/e2e/invitations/editInvitation.cy.js index d9f3e5480..64ddf0bd2 100644 --- a/cypress/e2e/invitations/editInvitation.cy.js +++ b/cypress/e2e/invitations/editInvitation.cy.js @@ -1,4 +1,3 @@ -import { PERMISSION_LEVELS } from '../../../src/enums'; import { buildItemPath } from '../../../src/config/paths'; import { buildInvitationTableRowSelector, @@ -6,6 +5,7 @@ import { buildShareButtonId, ITEM_MEMBERSHIP_PERMISSION_SELECT_CLASS, } from '../../../src/config/selectors'; +import { PERMISSION_LEVELS } from '../../fixtures/enum'; import { ITEMS_WITH_INVITATIONS } from '../../fixtures/invitations'; import { TABLE_MEMBERSHIP_RENDER_TIME } from '../../support/constants'; diff --git a/cypress/e2e/item/create/createShortcut.cy.js b/cypress/e2e/item/create/createShortcut.cy.js index 83da545d2..99b91e692 100644 --- a/cypress/e2e/item/create/createShortcut.cy.js +++ b/cypress/e2e/item/create/createShortcut.cy.js @@ -7,8 +7,9 @@ import { buildItemMenu, buildItemMenuButtonId, } from '../../../../src/config/selectors'; -import { ITEM_LAYOUT_MODES, ITEM_TYPES } from '../../../../src/enums'; +import { ITEM_LAYOUT_MODES } from '../../../../src/enums'; import { buildShortcutExtra } from '../../../../src/utils/itemExtra'; +import { ITEM_TYPES } from '../../../fixtures/enum'; import { IMAGE_ITEM_DEFAULT } from '../../../fixtures/files'; import { SAMPLE_ITEMS } from '../../../fixtures/items'; import { TABLE_ITEM_RENDER_TIME } from '../../../support/constants'; diff --git a/cypress/e2e/item/view/viewFile.cy.js b/cypress/e2e/item/view/viewFile.cy.js index 0d34294c2..58bba397d 100644 --- a/cypress/e2e/item/view/viewFile.cy.js +++ b/cypress/e2e/item/view/viewFile.cy.js @@ -12,7 +12,7 @@ import { } from '../../../fixtures/files'; import { expectFileViewScreenLayout } from '../../../support/viewUtils'; -describe('Files', () => { +describe('View Files', () => { describe('default server', () => { beforeEach(() => { cy.setUpApi({ diff --git a/cypress/e2e/item/view/viewFolder.cy.js b/cypress/e2e/item/view/viewFolder.cy.js index 721d25985..a0792b3f6 100644 --- a/cypress/e2e/item/view/viewFolder.cy.js +++ b/cypress/e2e/item/view/viewFolder.cy.js @@ -47,7 +47,7 @@ describe('View Folder', () => { i18n.changeLanguage(CURRENT_USER.extra.lang); }); - it('visit Home', () => { + it.only('visit Home', () => { cy.visit(HOME_PATH); cy.switchMode(ITEM_LAYOUT_MODES.GRID); diff --git a/cypress/e2e/memberships/createItemMembership.cy.js b/cypress/e2e/memberships/createItemMembership.cy.js index ac6b03756..93995a3cc 100644 --- a/cypress/e2e/memberships/createItemMembership.cy.js +++ b/cypress/e2e/memberships/createItemMembership.cy.js @@ -1,4 +1,3 @@ -import { PERMISSION_LEVELS } from '../../../src/enums'; import { buildItemPath } from '../../../src/config/paths'; import { buildShareButtonId, @@ -6,6 +5,7 @@ import { SHARE_ITEM_EMAIL_INPUT_ID, SHARE_ITEM_SHARE_BUTTON_ID, } from '../../../src/config/selectors'; +import { PERMISSION_LEVELS } from '../../fixtures/enum'; import { SAMPLE_ITEMS } from '../../fixtures/items'; import { MEMBERS } from '../../fixtures/members'; diff --git a/cypress/e2e/memberships/editItemMembership.cy.js b/cypress/e2e/memberships/editItemMembership.cy.js index 9035b3317..a982f1e65 100644 --- a/cypress/e2e/memberships/editItemMembership.cy.js +++ b/cypress/e2e/memberships/editItemMembership.cy.js @@ -1,4 +1,3 @@ -import { PERMISSION_LEVELS } from '../../../src/enums'; import { buildItemPath } from '../../../src/config/paths'; import { buildItemMembershipRowSelector, @@ -6,6 +5,7 @@ import { buildShareButtonId, ITEM_MEMBERSHIP_PERMISSION_SELECT_CLASS, } from '../../../src/config/selectors'; +import { PERMISSION_LEVELS } from '../../fixtures/enum'; import { ITEMS_WITH_MEMBERSHIPS } from '../../fixtures/memberships'; import { TABLE_MEMBERSHIP_RENDER_TIME } from '../../support/constants'; diff --git a/cypress/fixtures/apps.js b/cypress/fixtures/apps.js index 003e01186..55173c30e 100644 --- a/cypress/fixtures/apps.js +++ b/cypress/fixtures/apps.js @@ -1,5 +1,5 @@ import { APP_NAME } from './apps/apps'; -import { ITEM_TYPES } from '../../src/enums'; +import { ITEM_TYPES } from './enum'; import { DEFAULT_FOLDER_ITEM } from './items'; import { CURRENT_USER } from './members'; diff --git a/cypress/fixtures/documents.js b/cypress/fixtures/documents.js index 3a09c5aa1..3a6a7f14d 100644 --- a/cypress/fixtures/documents.js +++ b/cypress/fixtures/documents.js @@ -1,4 +1,4 @@ -import { ITEM_TYPES } from '../../src/enums'; +import { ITEM_TYPES } from './enum'; import { buildDocumentExtra } from '../../src/utils/itemExtra'; import { CURRENT_USER } from './members'; import { DEFAULT_FOLDER_ITEM } from './items'; diff --git a/src/enums/itemTypes.js b/cypress/fixtures/enum.js similarity index 63% rename from src/enums/itemTypes.js rename to cypress/fixtures/enum.js index d5e98b7db..721d6e090 100644 --- a/src/enums/itemTypes.js +++ b/cypress/fixtures/enum.js @@ -1,7 +1,18 @@ /** * @deprecated use graasp sdk + * this object only exists for tests */ -const ITEM_TYPES = { +export const PERMISSION_LEVELS = { + READ: 'read', + WRITE: 'write', + ADMIN: 'admin', +}; + +/** + * @deprecated use graasp sdk + * this object only exists for tests + */ +export const ITEM_TYPES = { FOLDER: 'folder', FILE: 'file', S3_FILE: 's3File', @@ -14,9 +25,3 @@ const ITEM_TYPES = { // but is used for the creation modal ZIP: 'zip', }; - -Object.freeze(ITEM_TYPES); -/** - * @deprecated use graasp sdk - */ -export default ITEM_TYPES; diff --git a/cypress/fixtures/files.js b/cypress/fixtures/files.js index 41938a2ec..58c799b0b 100644 --- a/cypress/fixtures/files.js +++ b/cypress/fixtures/files.js @@ -1,4 +1,5 @@ -import { ITEM_TYPES, MIME_TYPES } from '../../src/enums'; +import { MIME_TYPES } from '../../src/enums'; +import { ITEM_TYPES } from './enum'; import { buildFileExtra, buildS3FileExtra } from '../../src/utils/itemExtra'; import { MOCK_IMAGE_URL, MOCK_PDF_URL, MOCK_VIDEO_URL } from './fileLinks'; import { CURRENT_USER } from './members'; diff --git a/cypress/fixtures/invitations.js b/cypress/fixtures/invitations.js index d1ed663db..13b5e5ebd 100644 --- a/cypress/fixtures/invitations.js +++ b/cypress/fixtures/invitations.js @@ -1,6 +1,6 @@ import { v4 } from 'uuid'; -import { PERMISSION_LEVELS } from '../../src/enums'; +import { PERMISSION_LEVELS } from './enum'; import { DEFAULT_FOLDER_ITEM } from './items'; import { MEMBERS } from './members'; diff --git a/cypress/fixtures/items.js b/cypress/fixtures/items.js index cc29ee24b..c3c97c49b 100644 --- a/cypress/fixtures/items.js +++ b/cypress/fixtures/items.js @@ -1,5 +1,5 @@ import { SETTINGS } from '../../src/config/constants'; -import { ITEM_TYPES, PERMISSION_LEVELS } from '../../src/enums'; +import { ITEM_TYPES, PERMISSION_LEVELS } from './enum'; import { buildItemLoginSchemaExtra } from '../../src/utils/itemExtra'; import { DEFAULT_TAGS, diff --git a/cypress/fixtures/links.js b/cypress/fixtures/links.js index 52eb4e637..b6e0474b5 100644 --- a/cypress/fixtures/links.js +++ b/cypress/fixtures/links.js @@ -1,4 +1,4 @@ -import { ITEM_TYPES } from '../../src/enums'; +import { ITEM_TYPES } from './enum'; import { buildEmbeddedLinkExtra } from '../../src/utils/itemExtra'; import { CURRENT_USER } from './members'; diff --git a/cypress/fixtures/memberships.js b/cypress/fixtures/memberships.js index 766c204b7..54ce876e4 100644 --- a/cypress/fixtures/memberships.js +++ b/cypress/fixtures/memberships.js @@ -1,4 +1,4 @@ -import { PERMISSION_LEVELS } from '../../src/enums'; +import { PERMISSION_LEVELS } from './enum'; import { DEFAULT_FOLDER_ITEM } from './items'; import { MEMBERS } from './members'; diff --git a/cypress/fixtures/navigationItems.js b/cypress/fixtures/navigationItems.js index f866a93d0..1e29254c0 100644 --- a/cypress/fixtures/navigationItems.js +++ b/cypress/fixtures/navigationItems.js @@ -1,4 +1,4 @@ -import { ITEM_TYPES } from '../../src/enums'; +import { ITEM_TYPES } from './enum'; import { buildDocumentExtra } from '../../src/utils/itemExtra'; import { DEFAULT_FOLDER_ITEM } from './items'; import { CURRENT_USER } from './members'; diff --git a/cypress/fixtures/recycleBin.js b/cypress/fixtures/recycleBin.js index db3a1ec1b..b69a51c1c 100644 --- a/cypress/fixtures/recycleBin.js +++ b/cypress/fixtures/recycleBin.js @@ -1,4 +1,4 @@ -import { PERMISSION_LEVELS } from '../../src/enums'; +import { PERMISSION_LEVELS } from './enum'; import { DEFAULT_FOLDER_ITEM } from './items'; import { CURRENT_USER } from './members'; diff --git a/cypress/fixtures/sharedItems.js b/cypress/fixtures/sharedItems.js index 46ccaefd7..e89b981dc 100644 --- a/cypress/fixtures/sharedItems.js +++ b/cypress/fixtures/sharedItems.js @@ -1,4 +1,4 @@ -import { PERMISSION_LEVELS } from '../../src/enums'; +import { PERMISSION_LEVELS } from './enum'; import { DEFAULT_FOLDER_ITEM } from './items'; import { MEMBERS } from './members'; diff --git a/cypress/fixtures/thumbnails.js b/cypress/fixtures/thumbnails.js index aaf3edfdd..864c4851b 100644 --- a/cypress/fixtures/thumbnails.js +++ b/cypress/fixtures/thumbnails.js @@ -1,5 +1,5 @@ -import { PERMISSION_LEVELS } from '../../src/enums'; import { FIXTURES_THUMBNAILS_FOLDER } from '../support/constants'; +import { PERMISSION_LEVELS } from './enum'; import { DEFAULT_FOLDER_ITEM } from './items'; import { MEMBERS } from './members'; diff --git a/cypress/support/createUtils.js b/cypress/support/createUtils.js index 90d4c1d91..c655793b0 100644 --- a/cypress/support/createUtils.js +++ b/cypress/support/createUtils.js @@ -9,7 +9,7 @@ import { DASHBOARD_UPLOADER_ID, ZIP_DASHBOARD_UPLOADER_ID, } from '../../src/config/selectors'; -import { ITEM_TYPES } from '../../src/enums'; +import { ITEM_TYPES } from '../fixtures/enum'; // eslint-disable-next-line import/prefer-default-export export const createItem = (payload, options) => { diff --git a/cypress/support/editUtils.js b/cypress/support/editUtils.js index 5cc3738f7..055ca91a7 100644 --- a/cypress/support/editUtils.js +++ b/cypress/support/editUtils.js @@ -4,7 +4,8 @@ import { buildEditButtonId, buildSaveButtonId, } from '../../src/config/selectors'; -import { ITEM_LAYOUT_MODES, ITEM_TYPES } from '../../src/enums'; +import { ITEM_LAYOUT_MODES } from '../../src/enums'; +import { ITEM_TYPES } from '../fixtures/enum'; import { CAPTION_EDIT_PAUSE, TABLE_ITEM_RENDER_TIME } from './constants'; // eslint-disable-next-line import/prefer-default-export diff --git a/cypress/support/server.js b/cypress/support/server.js index c382d7802..36efdec6a 100644 --- a/cypress/support/server.js +++ b/cypress/support/server.js @@ -17,7 +17,7 @@ import { SIGN_IN_PATH, THUMBNAIL_EXTENSION, } from '../../src/config/constants'; -import { PERMISSION_LEVELS } from '../../src/enums'; +import { PERMISSION_LEVELS } from '../fixtures/enum'; import { getItemById, getParentsIdsFromPath, diff --git a/package.json b/package.json index 6e15c0558..b6646fd39 100644 --- a/package.json +++ b/package.json @@ -8,9 +8,9 @@ "@emotion/styled": "11.10.5", "@graasp/chatbox": "1.0.1", "@graasp/query-client": "0.1.2", - "@graasp/sdk": "0.2.0", + "@graasp/sdk": "0.3.0", "@graasp/translations": "1.3.0", - "@graasp/ui": "0.7.1", + "@graasp/ui": "0.8.0", "@mui/icons-material": "5.11.0", "@mui/lab": "5.0.0-alpha.117", "@mui/material": "5.11.6", @@ -150,8 +150,8 @@ "react-error-overlay": "6.0.9", "@mui/icons-material": "5.10.14", "@mui/material": "5.10.14", - "@graasp/sdk": "0.2.0", - "react-query": "3.39.2" + "react-query": "3.39.2", + "@graasp/sdk": "0.3.0" }, "packageManager": "yarn@3.2.4" } diff --git a/src/components/common/Chatbox.js b/src/components/common/Chatbox.js index 9bd0fdcb3..1d38dac58 100644 --- a/src/components/common/Chatbox.js +++ b/src/components/common/Chatbox.js @@ -5,11 +5,11 @@ import { useContext } from 'react'; import GraaspChatbox from '@graasp/chatbox'; import { MUTATION_KEYS } from '@graasp/query-client'; +import { PermissionLevel } from '@graasp/sdk'; import { Loader } from '@graasp/ui'; import { hooks, useMutation } from '../../config/queryClient'; import { CHATBOX_ID, CHATBOX_INPUT_BOX_ID } from '../../config/selectors'; -import { PERMISSION_LEVELS } from '../../enums'; import { CurrentUserContext } from '../context/CurrentUserContext'; const { useItemChat, useMembers, useAvatar, useItemMemberships } = hooks; @@ -46,7 +46,7 @@ const Chatbox = ({ item }) => { // only show export chat when user has admin right on the item const isAdmin = itemPermissions?.find((perms) => perms.memberId === currentMember.id) - ?.permission === PERMISSION_LEVELS.ADMIN; + ?.permission === PermissionLevel.Admin; return ( void; +}>({}); -const CopyItemModalProvider = ({ children }) => { +const CopyItemModalProvider = ({ + children, +}: { + children: JSX.Element | JSX.Element[]; +}): JSX.Element => { const { t: translateBuilder } = useBuilderTranslation(); const { mutate: copyItems } = useMutation(MUTATION_KEYS.COPY_ITEMS); - const [open, setOpen] = useState(false); - const [itemIds, setItemIds] = useState(false); + const [open, setOpen] = useState(false); + const [itemIds, setItemIds] = useState(null); const openModal = (newItemIds) => { setOpen(true); @@ -64,12 +69,4 @@ const CopyItemModalProvider = ({ children }) => { ); }; -CopyItemModalProvider.propTypes = { - children: PropTypes.node, -}; - -CopyItemModalProvider.defaultProps = { - children: null, -}; - export { CopyItemModalProvider, CopyItemModalContext }; diff --git a/src/components/context/CreateShortcutModalContext.tsx b/src/components/context/CreateShortcutModalContext.tsx index b7c60bf2d..a90dd037b 100644 --- a/src/components/context/CreateShortcutModalContext.tsx +++ b/src/components/context/CreateShortcutModalContext.tsx @@ -40,12 +40,12 @@ const CreateShortcutModalProvider: FC = ({ children }) => { setItem(null); }; - const onConfirm = ({ id: target, to }) => { + const onConfirm = ({ ids: [target], to }) => { const shortcut = { name: translateBuilder(BUILDER.CREATE_SHORTCUT_DEFAULT_NAME, { name: item.name, }), - extra: buildShortcutExtra(target[0]), + extra: buildShortcutExtra(target), type: ItemType.SHORTCUT, // set parent id if not root parentId: to !== TREE_MODAL_MY_ITEMS_ID ? to : undefined, diff --git a/src/components/context/CurrentUserContext.js b/src/components/context/CurrentUserContext.tsx similarity index 78% rename from src/components/context/CurrentUserContext.js rename to src/components/context/CurrentUserContext.tsx index 03bc2cadb..bcd2e80d4 100644 --- a/src/components/context/CurrentUserContext.js +++ b/src/components/context/CurrentUserContext.tsx @@ -5,14 +5,18 @@ import { createContext, useEffect, useMemo } from 'react'; import i18n from '../../config/i18n'; import { hooks } from '../../config/queryClient'; -const CurrentUserContext = createContext(); +const CurrentUserContext = createContext(null); + +type Props = { + children: JSX.Element | JSX.Element[]; +}; const { useCurrentMember } = hooks; -const CurrentUserContextProvider = ({ children }) => { +const CurrentUserContextProvider = ({ children }: Props): JSX.Element => { const query = useCurrentMember(); // update language depending on user setting - const lang = query?.data?.extra?.lang; + const lang = query?.data?.extra?.lang as string; useEffect(() => { if (lang !== i18n.language) { i18n.changeLanguage(lang); diff --git a/src/components/context/FlagItemModalContext.js b/src/components/context/FlagItemModalContext.tsx similarity index 75% rename from src/components/context/FlagItemModalContext.js rename to src/components/context/FlagItemModalContext.tsx index 9f2c2d398..87ac9bea3 100644 --- a/src/components/context/FlagItemModalContext.js +++ b/src/components/context/FlagItemModalContext.tsx @@ -1,6 +1,6 @@ -import PropTypes from 'prop-types'; +import { List as ImmutableList } from 'immutable'; -import { ListItem, ListItemText } from '@mui/material'; +import { ListItemButton, ListItemText } from '@mui/material'; import Dialog from '@mui/material/Dialog'; import DialogActions from '@mui/material/DialogActions'; import DialogContent from '@mui/material/DialogContent'; @@ -11,6 +11,7 @@ import Typography from '@mui/material/Typography'; import { createContext, useMemo, useState } from 'react'; import { MUTATION_KEYS } from '@graasp/query-client'; +import { FlagRecord } from '@graasp/query-client/dist/types'; import { BUILDER } from '@graasp/translations'; import { Button } from '@graasp/ui'; @@ -25,16 +26,27 @@ import CancelButton from '../common/CancelButton'; const { useFlags } = hooks; -const FlagItemModalContext = createContext(); +const FlagItemModalContext = createContext<{ + openModal?: (id: string) => void; +}>({}); -const FlagItemModalProvider = ({ children }) => { +const FlagItemModalProvider = ({ + children, +}: { + children: JSX.Element | JSX.Element[]; +}): JSX.Element => { const { t: translateBuilder } = useBuilderTranslation(); - const { mutate: postFlagItem } = useMutation(MUTATION_KEYS.POST_ITEM_FLAG); + const { mutate: postFlagItem } = useMutation< + unknown, + unknown, + { flagId: string; itemId: string } + >(MUTATION_KEYS.POST_ITEM_FLAG); const [open, setOpen] = useState(false); - const [selectedFlag, setSelectedFlag] = useState(false); - const [itemId, setItemId] = useState(false); + const [selectedFlag, setSelectedFlag] = useState(null); + const [itemId, setItemId] = useState(null); - const { data: flags } = useFlags(); + const { data } = useFlags(); + const flags = data as ImmutableList; const openModal = (newItemId) => { setOpen(true); @@ -77,15 +89,14 @@ const FlagItemModalProvider = ({ children }) => { }} > {flags?.map((flag) => ( - - + ))} @@ -105,12 +116,4 @@ const FlagItemModalProvider = ({ children }) => { ); }; -FlagItemModalProvider.propTypes = { - children: PropTypes.node, -}; - -FlagItemModalProvider.defaultProps = { - children: null, -}; - export { FlagItemModalProvider, FlagItemModalContext }; diff --git a/src/components/context/LayoutContext.tsx b/src/components/context/LayoutContext.tsx index 1029adf61..1651c4d22 100644 --- a/src/components/context/LayoutContext.tsx +++ b/src/components/context/LayoutContext.tsx @@ -2,7 +2,7 @@ import { createContext, useMemo, useState } from 'react'; import { DEFAULT_ITEM_LAYOUT_MODE } from '../../config/constants'; -import { CHAT_STATUS, ITEM_LAYOUT_MODES } from '../../enums'; +import { ChatStatus, ITEM_LAYOUT_MODES } from '../../enums'; interface LayoutContextInterface { mode: string; @@ -67,8 +67,7 @@ const LayoutContextProvider = ({ const [isItemMetadataMenuOpen, setIsItemMetadataMenuOpen] = useState(false); // check query params to see if chat should be open const chatIsOpen = - new URLSearchParams(window.location.search).get('chat') === - CHAT_STATUS.OPEN; + new URLSearchParams(window.location.search).get('chat') === ChatStatus.OPEN; const [isChatboxMenuOpen, setIsChatboxMenuOpen] = useState(chatIsOpen); const value: LayoutContextInterface = useMemo( diff --git a/src/components/context/MoveItemModalContext.js b/src/components/context/MoveItemModalContext.tsx similarity index 81% rename from src/components/context/MoveItemModalContext.js rename to src/components/context/MoveItemModalContext.tsx index 381cba2bf..506b1da5e 100644 --- a/src/components/context/MoveItemModalContext.js +++ b/src/components/context/MoveItemModalContext.tsx @@ -1,4 +1,3 @@ -import PropTypes from 'prop-types'; import { validate } from 'uuid'; import { createContext, useMemo, useState } from 'react'; @@ -11,14 +10,22 @@ import { useMutation } from '../../config/queryClient'; import { TREE_PREVENT_SELECTION } from '../../enums'; import TreeModal from '../main/TreeModal'; -const MoveItemModalContext = createContext(); +type Value = { + openModal?: (ids: string[]) => void; +}; + +const MoveItemModalContext = createContext({}); -const MoveItemModalProvider = ({ children }) => { +const MoveItemModalProvider = ({ + children, +}: { + children: JSX.Element | JSX.Element[]; +}): JSX.Element => { const { t: translateBuilder } = useBuilderTranslation(); const { mutate: moveItems } = useMutation(MUTATION_KEYS.MOVE_ITEMS); const [open, setOpen] = useState(false); - const [itemIds, setItemIds] = useState(false); + const [itemIds, setItemIds] = useState(null); const openModal = (newItemIds) => { setOpen(true); @@ -57,7 +64,7 @@ const MoveItemModalProvider = ({ children }) => { ); }; - const value = useMemo(() => ({ openModal }), []); + const value = useMemo(() => ({ openModal }), []); return ( @@ -67,12 +74,4 @@ const MoveItemModalProvider = ({ children }) => { ); }; -MoveItemModalProvider.propTypes = { - children: PropTypes.node, -}; - -MoveItemModalProvider.defaultProps = { - children: null, -}; - export { MoveItemModalProvider, MoveItemModalContext }; diff --git a/src/components/file/FileUploader.js b/src/components/file/FileUploader.js index eb25c55e5..16c95a93b 100644 --- a/src/components/file/FileUploader.js +++ b/src/components/file/FileUploader.js @@ -1,40 +1,61 @@ import '@uppy/drag-drop/dist/style.css'; import { DragDrop } from '@uppy/react'; -import { Container, styled } from '@mui/material'; +import { Box, styled } from '@mui/material'; import { useContext, useEffect, useState } from 'react'; import { MAX_FILE_SIZE } from '@graasp/sdk'; import { BUILDER } from '@graasp/translations'; -import { FILE_UPLOAD_MAX_FILES, HEADER_HEIGHT } from '../../config/constants'; +import { + DRAWER_WIDTH, + FILE_UPLOAD_MAX_FILES, + HEADER_HEIGHT, +} from '../../config/constants'; import { useBuilderTranslation } from '../../config/i18n'; import { UPLOADER_ID } from '../../config/selectors'; import { humanFileSize } from '../../utils/uppy'; +import { LayoutContext } from '../context/LayoutContext'; import { UppyContext } from './UppyContext'; -const StyledContainer = styled(Container)(({ theme }) => ({ +const StyledContainer = styled(Box)(({ theme, isMainMenuOpen }) => ({ display: 'none', height: '100vh', width: '100%', boxSizing: 'border-box', - position: 'absolute', + position: 'fixed', top: 0, - padding: `${HEADER_HEIGHT + theme.spacing(3)}px ${theme.spacing( - 3, - )}px ${theme.spacing(3)}px`, left: 0, // show above drawer zIndex: theme.zIndex.drawer + 1, opacity: 0.8, - '& div': { + '& > div': { width: '100%', + height: '100vh', + boxSizing: 'border-box', + paddingLeft: `${ + Number(theme.spacing(2).slice(0, -2)) + + (isMainMenuOpen ? DRAWER_WIDTH : 0) + }px`, + paddingTop: `${Number(theme.spacing(2).slice(0, -2)) + HEADER_HEIGHT}px`, + paddingBottom: theme.spacing(2), + paddingRight: theme.spacing(2), + + '& > div': { + boxSizing: 'border-box', + height: '100vh', + // container's top and bottom padding + paddingBottom: `${ + Number(theme.spacing(4).slice(0, -2)) + HEADER_HEIGHT + }px`, + }, }, })); const FileUploader = () => { + const { isMainMenuOpen } = useContext(LayoutContext); const { uppy } = useContext(UppyContext); const [isDragging, setIsDragging] = useState(false); const [isValid, setIsValid] = useState(true); @@ -118,6 +139,7 @@ const FileUploader = () => { onDragEnd={(e) => handleDragEnd(e)} onDragLeave={(e) => handleDragEnd(e)} onDrop={handleDrop} + isMainMenuOpen={isMainMenuOpen} > { +const StyledContainer = styled(Box)<{ open: boolean }>(({ theme, open }) => { const openStyles = open ? { transition: theme.transitions.create('margin', { @@ -49,7 +47,13 @@ const StyledContainer = styled(Box)(({ theme, open }) => { }; }); -const ItemMain = ({ id, children, item }) => { +type Props = { + children: JSX.Element | JSX.Element[]; + item: ItemRecord; + id?: string; +}; + +const ItemMain = ({ id, children, item }: Props): JSX.Element => { const { t: translateBuilder } = useBuilderTranslation(); const { isItemMetadataMenuOpen, @@ -114,14 +118,4 @@ const ItemMain = ({ id, children, item }) => { ); }; -ItemMain.propTypes = { - children: PropTypes.node.isRequired, - item: PropTypes.instanceOf(Record).isRequired, - id: PropTypes.string, -}; - -ItemMain.defaultProps = { - id: null, -}; - export default ItemMain; diff --git a/src/components/item/ItemMemberships.js b/src/components/item/ItemMemberships.tsx similarity index 82% rename from src/components/item/ItemMemberships.js rename to src/components/item/ItemMemberships.tsx index 1cec83084..e511bfdde 100644 --- a/src/components/item/ItemMemberships.js +++ b/src/components/item/ItemMemberships.tsx @@ -1,5 +1,3 @@ -import PropTypes from 'prop-types'; - import EditIcon from '@mui/icons-material/Edit'; import VisibilityIcon from '@mui/icons-material/Visibility'; import AvatarGroup from '@mui/material/AvatarGroup'; @@ -9,17 +7,27 @@ import Tooltip from '@mui/material/Tooltip'; import { useContext } from 'react'; +import { PermissionLevel } from '@graasp/sdk'; import { BUILDER } from '@graasp/translations'; import { useBuilderTranslation } from '../../config/i18n'; import { hooks } from '../../config/queryClient'; import { ITEM_MEMBERSHIPS_CONTENT_ID } from '../../config/selectors'; -import { PERMISSION_LEVELS } from '../../enums'; import { membershipsWithoutUser } from '../../utils/membership'; import MemberAvatar from '../common/MemberAvatar'; import { CurrentUserContext } from '../context/CurrentUserContext'; -const ItemMemberships = ({ id, maxAvatar, onClick }) => { +type Props = { + maxAvatar?: number; + id?: string; + onClick?: () => void; +}; + +const ItemMemberships = ({ + id, + maxAvatar = 2, + onClick, +}: Props): JSX.Element => { const { t: translateBuilder } = useBuilderTranslation(); const { data: memberships, isError } = hooks.useItemMemberships(id); const { data: currentUser } = useContext(CurrentUserContext); @@ -43,12 +51,7 @@ const ItemMemberships = ({ id, maxAvatar, onClick }) => { } return ( - + { {filteredMemberships.map(({ memberId, permission }) => { const badgeContent = - permission === PERMISSION_LEVELS.READ ? ( + permission === PermissionLevel.Read ? ( ) : ( @@ -87,16 +90,4 @@ const ItemMemberships = ({ id, maxAvatar, onClick }) => { ); }; -ItemMemberships.propTypes = { - maxAvatar: PropTypes.number, - id: PropTypes.string, - onClick: PropTypes.func, -}; - -ItemMemberships.defaultProps = { - maxAvatar: 2, - id: null, - onClick: null, -}; - export default ItemMemberships; diff --git a/src/components/item/ItemMetadataContent.js b/src/components/item/ItemMetadataContent.tsx similarity index 78% rename from src/components/item/ItemMetadataContent.js rename to src/components/item/ItemMetadataContent.tsx index 712a7c2c6..70dbffd01 100644 --- a/src/components/item/ItemMetadataContent.js +++ b/src/components/item/ItemMetadataContent.tsx @@ -1,6 +1,3 @@ -import { Record } from 'immutable'; -import PropTypes from 'prop-types'; - import Table from '@mui/material/Table'; import TableBody from '@mui/material/TableBody'; import TableCell from '@mui/material/TableCell'; @@ -10,6 +7,14 @@ import Typography from '@mui/material/Typography'; import { useContext } from 'react'; +import { ItemRecord } from '@graasp/query-client/dist/types'; +import { + AppItemExtra, + EmbeddedLinkItemExtra, + ItemType, + LocalFileItemExtra, + S3FileItemExtra, +} from '@graasp/sdk'; import { BUILDER, COMMON } from '@graasp/translations'; import i18n, { @@ -21,33 +26,32 @@ import { ITEM_PANEL_NAME_ID, ITEM_PANEL_TABLE_ID, } from '../../config/selectors'; -import { ITEM_TYPES } from '../../enums'; import { formatDate } from '../../utils/date'; -import { getFileExtra, getS3FileExtra } from '../../utils/itemExtra'; import { LayoutContext } from '../context/LayoutContext'; import ItemMemberships from './ItemMemberships'; const { useMember } = hooks; -const ItemMetadataContent = ({ item }) => { +type Props = { item: ItemRecord }; + +const ItemMetadataContent = ({ item }: Props): JSX.Element => { const { t: translateBuilder } = useBuilderTranslation(); const { t: translateCommon } = useCommonTranslation(); - const { setIsItemSharingOpen } = useContext(LayoutContext); + const { setIsItemMetadataMenuOpen } = useContext(LayoutContext); const { data: creator } = useMember(item.creator); const onClick = () => { - setIsItemSharingOpen(true); + setIsItemMetadataMenuOpen(true); }; - let { type } = item; + const { type, extra } = item; let size = null; - if (type === ITEM_TYPES.S3_FILE) { - const extra = getS3FileExtra(item.extra); - ({ mimetype: type, size } = extra); - } else if (type === ITEM_TYPES.FILE) { - const extra = getFileExtra(item.extra); - ({ mimetype: type, size } = extra); + let mimetype = null; + if (type === ItemType.S3_FILE) { + ({ mimetype, size } = extra[ItemType.S3_FILE] as S3FileItemExtra); + } else if (type === ItemType.LOCAL_FILE) { + ({ mimetype, size } = extra[ItemType.LOCAL_FILE] as LocalFileItemExtra); } const renderLink = () => { @@ -59,11 +63,13 @@ const ItemMetadataContent = ({ item }) => { {link} ); - if (type === ITEM_TYPES.APP) { - return buildTableRow(item.extra[ITEM_TYPES.APP].url); + if (type === ItemType.APP) { + return buildTableRow((item.extra[ItemType.APP] as AppItemExtra).url); } - if (type === ITEM_TYPES.LINK) { - return buildTableRow(item.extra[ITEM_TYPES.LINK].url); + if (type === ItemType.LINK) { + return buildTableRow( + (item.extra[ItemType.LINK] as EmbeddedLinkItemExtra).url, + ); } return null; }; @@ -84,7 +90,7 @@ const ItemMetadataContent = ({ item }) => { {translateBuilder(BUILDER.ITEM_METADATA_TYPE_TITLE)} - {type} + {mimetype ?? type} {size && ( @@ -131,8 +137,4 @@ const ItemMetadataContent = ({ item }) => { ); }; -ItemMetadataContent.propTypes = { - item: PropTypes.instanceOf(Record).isRequired, -}; - export default ItemMetadataContent; diff --git a/src/components/item/ItemPanel.js b/src/components/item/ItemPanel.tsx similarity index 68% rename from src/components/item/ItemPanel.js rename to src/components/item/ItemPanel.tsx index 83a4cccfa..2faecbca4 100644 --- a/src/components/item/ItemPanel.js +++ b/src/components/item/ItemPanel.tsx @@ -1,5 +1,3 @@ -import PropTypes from 'prop-types'; - import { Toolbar, styled } from '@mui/material'; import Drawer from '@mui/material/Drawer'; @@ -17,7 +15,9 @@ const StyledDrawer = styled(Drawer)(({ theme }) => ({ }, })); -const ItemPanel = ({ open, children }) => ( +type Props = { open: boolean; children: JSX.Element | JSX.Element[] }; + +const ItemPanel = ({ open, children }: Props): JSX.Element => ( ( ); -ItemPanel.propTypes = { - open: PropTypes.bool.isRequired, - children: PropTypes.oneOfType([ - PropTypes.element, - PropTypes.arrayOf(PropTypes.element), - ]), -}; - -ItemPanel.defaultProps = { - children: null, -}; - export default ItemPanel; diff --git a/src/components/item/ItemSearch.tsx b/src/components/item/ItemSearch.tsx index 98dca7315..b9d8bfd89 100644 --- a/src/components/item/ItemSearch.tsx +++ b/src/components/item/ItemSearch.tsx @@ -1,10 +1,10 @@ -import { List, RecordOf } from 'immutable'; +import { List } from 'immutable'; import Typography from '@mui/material/Typography'; import { ChangeEvent, FC, useState } from 'react'; -import { Item } from '@graasp/sdk'; +import { ItemRecord } from '@graasp/query-client/dist/types'; import { BUILDER } from '@graasp/translations'; import { SearchInput } from '@graasp/ui'; @@ -30,9 +30,9 @@ export const NoItemSearchResult: FC = () => { }; export const useItemSearch = ( - items: List>, + items: List, ): { - results: List>; + results: List; text: string; input: JSX.Element; } => { diff --git a/src/components/item/form/DocumentForm.js b/src/components/item/form/DocumentForm.tsx similarity index 68% rename from src/components/item/form/DocumentForm.js rename to src/components/item/form/DocumentForm.tsx index 02603f96c..9ec9b0c50 100644 --- a/src/components/item/form/DocumentForm.js +++ b/src/components/item/form/DocumentForm.tsx @@ -1,17 +1,26 @@ -import PropTypes from 'prop-types'; - import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; +import { DocumentItemExtra, Item, ItemType } from '@graasp/sdk'; import { BUILDER } from '@graasp/translations'; import { TextEditor } from '@graasp/ui'; import { useBuilderTranslation } from '../../../config/i18n'; import { ITEM_FORM_DOCUMENT_TEXT_ID } from '../../../config/selectors'; -import { buildDocumentExtra, getDocumentExtra } from '../../../utils/itemExtra'; +import { buildDocumentExtra } from '../../../utils/itemExtra'; import BaseForm from './BaseItemForm'; -const DocumentForm = ({ onChange, item, updatedProperties }) => { +type Props = { + onChange: (item: Partial) => void; + item: Partial; + updatedProperties: Partial; +}; + +const DocumentForm = ({ + onChange, + item, + updatedProperties, +}: Props): JSX.Element => { const { t: translateBuilder } = useBuilderTranslation(); const handleOnChange = (content) => { @@ -22,8 +31,10 @@ const DocumentForm = ({ onChange, item, updatedProperties }) => { }; const value = - getDocumentExtra(updatedProperties?.extra)?.content || - getDocumentExtra(item?.extra)?.content; + ((updatedProperties?.extra?.[ItemType.DOCUMENT] as DocumentItemExtra) + ?.content as string) || + ((item?.extra?.[ItemType.DOCUMENT] as DocumentItemExtra) + ?.content as string); return ( <> @@ -49,14 +60,4 @@ const DocumentForm = ({ onChange, item, updatedProperties }) => { ); }; -DocumentForm.propTypes = { - onChange: PropTypes.func.isRequired, - item: PropTypes.shape({ - extra: PropTypes.shape({}), - }).isRequired, - updatedProperties: PropTypes.shape({ - extra: PropTypes.shape({}), - }).isRequired, -}; - export default DocumentForm; diff --git a/src/components/item/form/FolderForm.js b/src/components/item/form/FolderForm.tsx similarity index 57% rename from src/components/item/form/FolderForm.js rename to src/components/item/form/FolderForm.tsx index 7b4d243fd..9051f55af 100644 --- a/src/components/item/form/FolderForm.js +++ b/src/components/item/form/FolderForm.tsx @@ -1,13 +1,22 @@ -import PropTypes from 'prop-types'; - import Box from '@mui/material/Box'; +import { Item } from '@graasp/sdk'; import { TextEditor } from '@graasp/ui'; import { FOLDER_FORM_DESCRIPTION_ID } from '../../../config/selectors'; import BaseItemForm from './BaseItemForm'; -const FolderForm = ({ onChange, item, updatedProperties }) => { +type Props = { + onChange: (item: Partial) => void; + item: Partial; + updatedProperties: Partial; +}; + +const FolderForm = ({ + onChange, + item, + updatedProperties, +}: Props): JSX.Element => { const onCaptionChange = (content) => { onChange({ ...updatedProperties, @@ -36,27 +45,4 @@ const FolderForm = ({ onChange, item, updatedProperties }) => { ); }; -FolderForm.propTypes = { - updatedProperties: PropTypes.shape({ - extra: PropTypes.shape({ - image: PropTypes.string, - }), - description: PropTypes.string, - }), - onChange: PropTypes.func.isRequired, - item: PropTypes.shape({ - name: PropTypes.string, - description: PropTypes.string, - type: PropTypes.string, - extra: PropTypes.shape({ - image: PropTypes.string, - }), - }), -}; - -FolderForm.defaultProps = { - item: {}, - updatedProperties: {}, -}; - export default FolderForm; diff --git a/src/components/item/form/LinkForm.js b/src/components/item/form/LinkForm.tsx similarity index 65% rename from src/components/item/form/LinkForm.js rename to src/components/item/form/LinkForm.tsx index 42c49582d..4d51c683b 100644 --- a/src/components/item/form/LinkForm.js +++ b/src/components/item/form/LinkForm.tsx @@ -1,31 +1,31 @@ -import PropTypes from 'prop-types'; - import { TextField } from '@mui/material'; import Typography from '@mui/material/Typography'; +import { EmbeddedLinkItemExtraProperties, Item, ItemType } from '@graasp/sdk'; import { BUILDER } from '@graasp/translations'; import { useBuilderTranslation } from '../../../config/i18n'; import { ITEM_FORM_LINK_INPUT_ID } from '../../../config/selectors'; -import { ITEM_TYPES } from '../../../enums'; import { isUrlValid } from '../../../utils/item'; -import { - buildEmbeddedLinkExtra, - getEmbeddedLinkExtra, -} from '../../../utils/itemExtra'; -const LinkForm = ({ onChange, item }) => { +type Props = { + onChange: (item: Partial) => void; + item: Partial; +}; + +const LinkForm = ({ onChange, item }: Props): JSX.Element => { const { t: translateBuilder } = useBuilderTranslation(); const handleLinkInput = (event) => { onChange({ ...item, name: translateBuilder(BUILDER.LINK_DEFAULT_NAME), // todo: this is replaced by iframely - extra: buildEmbeddedLinkExtra({ url: event.target.value }), + extra: { [ItemType.LINK]: { url: event.target.value } }, }); }; - const { url } = getEmbeddedLinkExtra(item.extra) || {}; + const { url } = + (item?.extra?.[ItemType.LINK] as EmbeddedLinkItemExtraProperties) || {}; const isLinkInvalid = url?.length && !isUrlValid(url); return ( @@ -51,17 +51,4 @@ const LinkForm = ({ onChange, item }) => { ); }; -LinkForm.propTypes = { - onChange: PropTypes.func.isRequired, - item: PropTypes.shape({ - name: PropTypes.string, - description: PropTypes.string, - extra: PropTypes.shape({ - [ITEM_TYPES.LINK]: PropTypes.shape({ - url: PropTypes.string, - }), - }), - }).isRequired, -}; - export default LinkForm; diff --git a/src/components/item/header/ItemHeader.js b/src/components/item/header/ItemHeader.tsx similarity index 69% rename from src/components/item/header/ItemHeader.js rename to src/components/item/header/ItemHeader.tsx index da08db834..5fca41d6f 100644 --- a/src/components/item/header/ItemHeader.js +++ b/src/components/item/header/ItemHeader.tsx @@ -1,5 +1,3 @@ -import PropTypes from 'prop-types'; - import Box from '@mui/material/Box'; import { useMatch } from 'react-router'; @@ -14,7 +12,17 @@ import ItemHeaderActions from './ItemHeaderActions'; const { useItem } = hooks; -const ItemHeader = ({ onClickMetadata, onClickChatbox, showNavigation }) => { +type Props = { + onClickMetadata?: () => void; + onClickChatbox?: () => void; + showNavigation?: boolean; +}; + +const ItemHeader = ({ + onClickMetadata, + onClickChatbox, + showNavigation = true, +}: Props): JSX.Element => { const match = useMatch(buildItemPath()); const itemId = match?.params?.itemId; const { data: item, isLoading: isItemLoading } = useItem(itemId); @@ -44,18 +52,4 @@ const ItemHeader = ({ onClickMetadata, onClickChatbox, showNavigation }) => { ); }; -ItemHeader.propTypes = { - onClickMetadata: PropTypes.func, - onClickChatbox: PropTypes.func, - showNavigation: PropTypes.bool, -}; - -ItemHeader.defaultProps = { - // eslint-disable-next-line @typescript-eslint/no-empty-function - onClickMetadata: () => {}, - // eslint-disable-next-line @typescript-eslint/no-empty-function - onClickChatbox: () => {}, - showNavigation: true, -}; - export default ItemHeader; diff --git a/src/components/item/header/ItemHeaderActions.js b/src/components/item/header/ItemHeaderActions.tsx similarity index 82% rename from src/components/item/header/ItemHeaderActions.js rename to src/components/item/header/ItemHeaderActions.tsx index 6f825d939..c0aba0e66 100644 --- a/src/components/item/header/ItemHeaderActions.js +++ b/src/components/item/header/ItemHeaderActions.tsx @@ -1,6 +1,3 @@ -import { Record } from 'immutable'; -import PropTypes from 'prop-types'; - import EditIcon from '@mui/icons-material/Edit'; import InfoIcon from '@mui/icons-material/Info'; import Box from '@mui/material/Box'; @@ -9,10 +6,15 @@ import Tooltip from '@mui/material/Tooltip'; import { useContext } from 'react'; +import { ItemRecord } from '@graasp/query-client/dist/types'; +import { ItemType } from '@graasp/sdk'; import { BUILDER } from '@graasp/translations'; import { ChatboxButton } from '@graasp/ui'; -import { ITEM_TYPES_WITH_CAPTIONS } from '../../../config/constants'; +import { + ITEM_ACTION_TABS, + ITEM_TYPES_WITH_CAPTIONS, +} from '../../../config/constants'; import { useBuilderTranslation } from '../../../config/i18n'; import { hooks } from '../../../config/queryClient'; import { @@ -21,7 +23,6 @@ import { ITEM_INFORMATION_ICON_IS_OPEN_CLASS, buildEditButtonId, } from '../../../config/selectors'; -import { ITEM_TYPES } from '../../../enums'; import { isItemUpdateAllowedForUser } from '../../../utils/membership'; import AnalyticsDashboardButton from '../../common/AnalyticsDashboardButton'; import PlayerViewButton from '../../common/PlayerViewButton'; @@ -34,12 +35,22 @@ import ModeButton from './ModeButton'; const { useItemMemberships } = hooks; -const ItemHeaderActions = ({ onClickMetadata, onClickChatbox, item }) => { +type Props = { + onClickMetadata: () => void; + onClickChatbox: () => void; + item: ItemRecord; +}; + +const ItemHeaderActions = ({ + onClickMetadata, + onClickChatbox, + item, +}: Props): JSX.Element => { const { t: translateBuilder } = useBuilderTranslation(); const { setEditingItemId, editingItemId, - isItemSettingsOpen, + openedActionTabId, isItemMetadataMenuOpen, } = useContext(LayoutContext); const id = item?.id; @@ -89,7 +100,7 @@ const ItemHeaderActions = ({ onClickMetadata, onClickChatbox, item }) => { return ( <> - {!isItemSettingsOpen && activeActions} + {openedActionTabId !== ITEM_ACTION_TABS.SETTINGS && activeActions} {canEdit && } ); @@ -99,7 +110,7 @@ const ItemHeaderActions = ({ onClickMetadata, onClickChatbox, item }) => { const renderTableActions = () => { // show only for content with tables : root or folders - if (type === ITEM_TYPES.FOLDER || !id) { + if (type === ItemType.FOLDER || !id) { return ; } return null; @@ -126,18 +137,4 @@ const ItemHeaderActions = ({ onClickMetadata, onClickChatbox, item }) => { ); }; -ItemHeaderActions.propTypes = { - onClickMetadata: PropTypes.func, - onClickChatbox: PropTypes.func, - item: PropTypes.instanceOf(Record), -}; - -ItemHeaderActions.defaultProps = { - // eslint-disable-next-line @typescript-eslint/no-empty-function - onClickMetadata: () => {}, - // eslint-disable-next-line @typescript-eslint/no-empty-function - onClickChatbox: () => {}, - item: Record({})(), -}; - export default ItemHeaderActions; diff --git a/src/components/item/header/ModeButton.js b/src/components/item/header/ModeButton.tsx similarity index 97% rename from src/components/item/header/ModeButton.js rename to src/components/item/header/ModeButton.tsx index 698e8148e..dd019674f 100644 --- a/src/components/item/header/ModeButton.js +++ b/src/components/item/header/ModeButton.tsx @@ -15,7 +15,7 @@ import { import { ITEM_LAYOUT_MODES } from '../../../enums'; import { LayoutContext } from '../../context/LayoutContext'; -const ModeButton = () => { +const ModeButton = (): JSX.Element => { const { t: translateBuilder } = useBuilderTranslation(); const { mode, setMode } = useContext(LayoutContext); diff --git a/src/components/item/settings/ItemSettingsButton.js b/src/components/item/settings/ItemSettingsButton.tsx similarity index 89% rename from src/components/item/settings/ItemSettingsButton.js rename to src/components/item/settings/ItemSettingsButton.tsx index e87dbdc71..efb3c80d0 100644 --- a/src/components/item/settings/ItemSettingsButton.js +++ b/src/components/item/settings/ItemSettingsButton.tsx @@ -1,5 +1,3 @@ -import PropTypes from 'prop-types'; - import CloseIcon from '@mui/icons-material/Close'; import SettingsIcon from '@mui/icons-material/Settings'; import IconButton from '@mui/material/IconButton'; @@ -17,7 +15,9 @@ import { } from '../../../config/selectors'; import { LayoutContext } from '../../context/LayoutContext'; -const ItemSettingsButton = ({ id }) => { +type Props = { id: string }; + +const ItemSettingsButton = ({ id }: Props): JSX.Element => { const { openedActionTabId, setOpenedActionTabId } = useContext(LayoutContext); const { t: translateBuilder } = useBuilderTranslation(); @@ -46,8 +46,4 @@ const ItemSettingsButton = ({ id }) => { ); }; -ItemSettingsButton.propTypes = { - id: PropTypes.string.isRequired, -}; - export default ItemSettingsButton; diff --git a/src/components/item/sharing/CreateItemMembershipForm.js b/src/components/item/sharing/CreateItemMembershipForm.tsx similarity index 83% rename from src/components/item/sharing/CreateItemMembershipForm.js rename to src/components/item/sharing/CreateItemMembershipForm.tsx index 6805ce1d8..29d5f3efd 100644 --- a/src/components/item/sharing/CreateItemMembershipForm.js +++ b/src/components/item/sharing/CreateItemMembershipForm.tsx @@ -1,5 +1,4 @@ -import { List, Record } from 'immutable'; -import PropTypes from 'prop-types'; +import { List } from 'immutable'; import validator from 'validator'; import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline'; @@ -10,6 +9,12 @@ import Tooltip from '@mui/material/Tooltip'; import { useState } from 'react'; import { MUTATION_KEYS, routines } from '@graasp/query-client'; +import { + Invitation, + ItemRecord, + MemberRecord, +} from '@graasp/query-client/dist/types'; +import { PermissionLevel } from '@graasp/sdk'; import { BUILDER } from '@graasp/translations'; import { Button } from '@graasp/ui'; @@ -21,25 +26,33 @@ import { SHARE_ITEM_EMAIL_INPUT_ID, SHARE_ITEM_SHARE_BUTTON_ID, } from '../../../config/selectors'; -import { PERMISSION_LEVELS } from '../../../enums'; import ItemMembershipSelect from './ItemMembershipSelect'; const { shareItemRoutine } = routines; +type Props = { item: ItemRecord; members: List }; + // todo: handle multiple invitations -const CreateItemMembershipForm = ({ item, members }) => { +const CreateItemMembershipForm = ({ item, members }: Props): JSX.Element => { const itemId = item.id; - const [error, setError] = useState(false); - - const { mutateAsync: shareItem } = useMutation(MUTATION_KEYS.SHARE_ITEM); + const [error, setError] = useState(false); + + const { mutateAsync: shareItem } = useMutation< + { failure?: { message: string }[] }, + unknown, + { + itemId: string; + data: { id: string; email: string; permission: PermissionLevel }[]; + } + >(MUTATION_KEYS.SHARE_ITEM); const { t: translateBuilder } = useBuilderTranslation(); - // use an array to later allow sending multiple invitations - const [invitation, setInvitation] = useState({ - permission: PERMISSION_LEVELS.READ, + // todo: use an array to later allow sending multiple invitations + const [invitation, setInvitation] = useState>({ + permission: PermissionLevel.Read, }); - const isInvitationInvalid = ({ email }) => { + const isInvitationInvalid = ({ email }: { email?: string }) => { // check mail validity if (!email) { return translateBuilder( @@ -174,12 +187,4 @@ const CreateItemMembershipForm = ({ item, members }) => { ); }; -CreateItemMembershipForm.propTypes = { - item: PropTypes.instanceOf(Record).isRequired, - members: PropTypes.instanceOf(List), -}; -CreateItemMembershipForm.defaultProps = { - members: List(), -}; - export default CreateItemMembershipForm; diff --git a/src/components/item/sharing/CsvInputParser.tsx b/src/components/item/sharing/CsvInputParser.tsx index adccf6de5..597926845 100644 --- a/src/components/item/sharing/CsvInputParser.tsx +++ b/src/components/item/sharing/CsvInputParser.tsx @@ -15,7 +15,7 @@ import Grid from '@mui/material/Grid'; import { FC, useState } from 'react'; import { MUTATION_KEYS } from '@graasp/query-client'; -import { Item } from '@graasp/sdk'; +import { Item, PermissionLevel } from '@graasp/sdk'; import { BUILDER, COMMON } from '@graasp/translations'; import { Button, Loader } from '@graasp/ui'; @@ -32,7 +32,6 @@ import { SHARE_ITEM_FROM_CSV_RESULT_FAILURES_ID, } from '../../../config/selectors'; import { Invitation } from '../../../config/types'; -import { PERMISSION_LEVELS } from '../../../enums'; const label = 'shareItemFromCsvLabel'; const allowedExtensions = ['.csv'].join(','); @@ -75,7 +74,7 @@ const CsvInputParser: FC = ({ item }) => { // add current item path and default permission read const dataWithItemPath = parsedData.map( (d: Partial) => ({ - permission: PERMISSION_LEVELS.READ, + permission: PermissionLevel.Read, ...d, itemPath, }), diff --git a/src/components/item/sharing/InvitationsTable.js b/src/components/item/sharing/InvitationsTable.tsx similarity index 82% rename from src/components/item/sharing/InvitationsTable.js rename to src/components/item/sharing/InvitationsTable.tsx index 981318a46..c9eb80f0e 100644 --- a/src/components/item/sharing/InvitationsTable.js +++ b/src/components/item/sharing/InvitationsTable.tsx @@ -1,9 +1,9 @@ -import { List, Record } from 'immutable'; -import PropTypes from 'prop-types'; +import { List } from 'immutable'; import { useMemo } from 'react'; import { MUTATION_KEYS } from '@graasp/query-client'; +import { Invitation, ItemRecord } from '@graasp/query-client/dist/types'; import { BUILDER } from '@graasp/translations'; import { Table as GraaspTable } from '@graasp/ui/dist/table'; @@ -26,17 +26,35 @@ const rowStyle = { alignItems: 'center', }; -const InvitationsTable = ({ invitations, item, emptyMessage }) => { +type Props = { + item: ItemRecord; + invitations: List; + emptyMessage?: string; +}; + +const InvitationsTable = ({ + invitations, + item, + emptyMessage, +}: Props): JSX.Element => { const { t: translateBuilder } = useBuilderTranslation(); - const { mutate: editInvitation } = useMutation( - MUTATION_KEYS.PATCH_INVITATION, - ); - const { mutate: postInvitations } = useMutation( - MUTATION_KEYS.POST_INVITATIONS, - ); - const { mutate: deleteInvitation } = useMutation( - MUTATION_KEYS.DELETE_INVITATION, - ); + const { mutate: editInvitation } = useMutation< + unknown, + unknown, + Partial & { + itemId: string; + } + >(MUTATION_KEYS.PATCH_INVITATION); + const { mutate: postInvitations } = useMutation< + unknown, + unknown, + { itemId: string; invitations: Partial[] } + >(MUTATION_KEYS.POST_INVITATIONS); + const { mutate: deleteInvitation } = useMutation< + unknown, + unknown, + { itemId: string; id: string } + >(MUTATION_KEYS.DELETE_INVITATION); const getRowId = ({ data }) => buildInvitationTableRowId(data.id); @@ -146,14 +164,4 @@ const InvitationsTable = ({ invitations, item, emptyMessage }) => { ); }; -InvitationsTable.propTypes = { - item: PropTypes.instanceOf(Record).isRequired, - invitations: PropTypes.instanceOf(List).isRequired, - emptyMessage: PropTypes.string, -}; - -InvitationsTable.defaultProps = { - emptyMessage: null, -}; - export default InvitationsTable; diff --git a/src/components/item/sharing/ItemMembershipsTable.js b/src/components/item/sharing/ItemMembershipsTable.tsx similarity index 82% rename from src/components/item/sharing/ItemMembershipsTable.js rename to src/components/item/sharing/ItemMembershipsTable.tsx index b30c56a13..0d908ea25 100644 --- a/src/components/item/sharing/ItemMembershipsTable.js +++ b/src/components/item/sharing/ItemMembershipsTable.tsx @@ -1,11 +1,10 @@ -import { Record } from 'immutable'; -import PropTypes from 'prop-types'; - import { Typography } from '@mui/material'; import { useMemo } from 'react'; import { MUTATION_KEYS } from '@graasp/query-client'; +import { ItemRecord } from '@graasp/query-client/dist/types'; +import { ItemMembership } from '@graasp/sdk'; import { BUILDER } from '@graasp/translations'; import { Loader } from '@graasp/ui'; import { Table as GraaspTable } from '@graasp/ui/dist/table'; @@ -33,53 +32,66 @@ const rowStyle = { }; const NameRenderer = (users) => { - const ChildComponent = ({ data: membership }) => { + const ChildComponent = ({ + data: membership, + }: { + data: Pick; + }) => { const user = users?.find(({ id }) => id === membership.memberId); return {user?.name ?? ''}; }; - ChildComponent.propTypes = { - data: PropTypes.shape({ - memberId: PropTypes.string.isRequired, - }).isRequired, - }; return ChildComponent; }; const EmailRenderer = (users) => { - const ChildComponent = ({ data: membership }) => { + const ChildComponent = ({ + data: membership, + }: { + data: Pick; + }) => { const user = users?.find(({ id }) => id === membership.memberId); return {user?.email ?? ''}; }; - ChildComponent.propTypes = { - data: PropTypes.shape({ - memberId: PropTypes.string.isRequired, - }).isRequired, - }; return ChildComponent; }; const getRowId = ({ data }) => buildItemMembershipRowId(data.id); +type Props = { + item: ItemRecord; + memberships: ItemMembership[]; + emptyMessage?: string; + showEmail?: boolean; +}; + const ItemMembershipsTable = ({ memberships, item, emptyMessage, - showEmail, -}) => { + showEmail = true, +}: Props): JSX.Element => { const { t: translateBuilder } = useBuilderTranslation(); const { data: users, isLoading } = hooks.useMembers( memberships.map(({ memberId }) => memberId), ); - const { mutate: deleteItemMembership } = useMutation( - MUTATION_KEYS.DELETE_ITEM_MEMBERSHIP, - ); - const { mutate: editItemMembership } = useMutation( - MUTATION_KEYS.EDIT_ITEM_MEMBERSHIP, - ); - const { mutate: shareItem } = useMutation(MUTATION_KEYS.POST_ITEM_MEMBERSHIP); + const { mutate: deleteItemMembership } = useMutation< + unknown, + unknown, + { itemId: string; id: string } + >(MUTATION_KEYS.DELETE_ITEM_MEMBERSHIP); + const { mutate: editItemMembership } = useMutation< + unknown, + unknown, + Partial + >(MUTATION_KEYS.EDIT_ITEM_MEMBERSHIP); + const { mutate: shareItem } = useMutation< + unknown, + unknown, + Partial & { email: string } + >(MUTATION_KEYS.POST_ITEM_MEMBERSHIP); const onDelete = ({ instance }) => { deleteItemMembership({ itemId: item.id, id: instance.id }); @@ -201,16 +213,4 @@ const ItemMembershipsTable = ({ ); }; -ItemMembershipsTable.propTypes = { - item: PropTypes.instanceOf(Record).isRequired, - memberships: PropTypes.arrayOf(PropTypes.shape({})).isRequired, - emptyMessage: PropTypes.string, - showEmail: PropTypes.bool, -}; - -ItemMembershipsTable.defaultProps = { - emptyMessage: null, - showEmail: true, -}; - export default ItemMembershipsTable; diff --git a/src/components/item/sharing/ItemSharingTab.js b/src/components/item/sharing/ItemSharingTab.tsx similarity index 89% rename from src/components/item/sharing/ItemSharingTab.js rename to src/components/item/sharing/ItemSharingTab.tsx index 3682eec00..6b1ec901e 100644 --- a/src/components/item/sharing/ItemSharingTab.js +++ b/src/components/item/sharing/ItemSharingTab.tsx @@ -1,6 +1,5 @@ -import { Record } from 'immutable'; +import { List } from 'immutable'; import partition from 'lodash.partition'; -import PropTypes from 'prop-types'; import { Divider } from '@mui/material'; import Container from '@mui/material/Container'; @@ -9,6 +8,7 @@ import Typography from '@mui/material/Typography'; import { useContext } from 'react'; +import { InvitationRecord, ItemRecord } from '@graasp/query-client/dist/types'; import { isPseudonymizedMember } from '@graasp/sdk'; import { BUILDER } from '@graasp/translations'; import { Loader } from '@graasp/ui'; @@ -25,13 +25,17 @@ import ItemMembershipsTable from './ItemMembershipsTable'; import SharingLink from './SharingLink'; import VisibilitySelect from './VisibilitySelect'; -const ItemSharingTab = ({ item }) => { +type Props = { + item: ItemRecord; +}; + +const ItemSharingTab = ({ item }: Props): JSX.Element => { const { t: translateBuilder } = useBuilderTranslation(); const { data: memberships } = hooks.useItemMemberships(item?.id); const { data: currentMember, isLoadingCurrentMember } = useContext(CurrentUserContext); const { data: members } = hooks.useMembers( - memberships?.map(({ memberId }) => memberId), + memberships?.map(({ memberId }) => memberId)?.toArray(), ); const { data: invitations } = hooks.useItemInvitations(item?.id); @@ -80,7 +84,8 @@ const ItemSharingTab = ({ item }) => { {/* show authenticated members if login schema is defined todo: show only if item is pseudomized */} - {getItemLoginSchema(item?.extra) && ( + {/* // todo: this will change with the refactor */} + {getItemLoginSchema(item?.extra as any) && ( <> @@ -105,7 +110,7 @@ const ItemSharingTab = ({ item }) => { } emptyMessage={translateBuilder( BUILDER.SHARING_INVITATIONS_EMPTY_MESSAGE, )} @@ -118,7 +123,7 @@ const ItemSharingTab = ({ item }) => { }; return ( - + {translateBuilder(BUILDER.SHARING_TITLE)} @@ -131,8 +136,5 @@ const ItemSharingTab = ({ item }) => { ); }; -ItemSharingTab.propTypes = { - item: PropTypes.instanceOf(Record).isRequired, -}; export default ItemSharingTab; diff --git a/src/components/item/sharing/ResendInvitationRenderer.js b/src/components/item/sharing/ResendInvitationRenderer.tsx similarity index 61% rename from src/components/item/sharing/ResendInvitationRenderer.js rename to src/components/item/sharing/ResendInvitationRenderer.tsx index 36293873f..5ed43739a 100644 --- a/src/components/item/sharing/ResendInvitationRenderer.js +++ b/src/components/item/sharing/ResendInvitationRenderer.tsx @@ -1,21 +1,28 @@ -import PropTypes from 'prop-types'; - import { useState } from 'react'; import { MUTATION_KEYS } from '@graasp/query-client'; +import { Invitation } from '@graasp/query-client/dist/types'; +import { Item } from '@graasp/sdk'; import { BUILDER } from '@graasp/translations'; import { Button } from '@graasp/ui'; import { useBuilderTranslation } from '../../../config/i18n'; import { useMutation } from '../../../config/queryClient'; -import { PERMISSION_LEVELS } from '../../../enums'; -const ResendInvitationRenderer = (item) => { +type ChildProps = { + data: Invitation; +}; + +const ResendInvitationRenderer = ( + item: Item, +): ((childProps: ChildProps) => JSX.Element) => { const { t: translateBuilder } = useBuilderTranslation(); const itemId = item.id; - const { mutate: resendInvitation } = useMutation( - MUTATION_KEYS.RESEND_INVITATION, - ); + const { mutate: resendInvitation } = useMutation< + unknown, + unknown, + { itemId: string; id: string } + >(MUTATION_KEYS.RESEND_INVITATION); const ChildComponent = ({ data: invitation }) => { const [clicked, setClicked] = useState(false); @@ -32,15 +39,6 @@ const ResendInvitationRenderer = (item) => { ); }; - ChildComponent.propTypes = { - data: PropTypes.shape({ - id: PropTypes.string.isRequired, - itemPath: PropTypes.string.isRequired, - permission: PropTypes.oneOf(Object.values(PERMISSION_LEVELS)).isRequired, - email: PropTypes.string.isRequired, - name: PropTypes.string, - }).isRequired, - }; return ChildComponent; }; diff --git a/src/components/item/sharing/TableRowDeleteButtonRenderer.js b/src/components/item/sharing/TableRowDeleteButtonRenderer.tsx similarity index 67% rename from src/components/item/sharing/TableRowDeleteButtonRenderer.js rename to src/components/item/sharing/TableRowDeleteButtonRenderer.tsx index ada255c43..75e0cb2b0 100644 --- a/src/components/item/sharing/TableRowDeleteButtonRenderer.js +++ b/src/components/item/sharing/TableRowDeleteButtonRenderer.tsx @@ -1,18 +1,27 @@ -import PropTypes from 'prop-types'; - import CloseIcon from '@mui/icons-material/Close'; -import IconButton from '@mui/material/IconButton'; +import IconButton, { IconButtonProps } from '@mui/material/IconButton'; import Tooltip from '@mui/material/Tooltip'; +import { Invitation } from '@graasp/query-client/dist/types'; +import { Item, ItemMembership } from '@graasp/sdk'; + import { useIsParentInstance } from '../../../utils/item'; +type Props = { + item: Item; + buildIdFunction: (id: string) => string; + tooltip?: string; + color?: IconButtonProps['color']; + onDelete?: (args: { instance: Invitation | ItemMembership }) => void; +}; + const TableRowDeleteButtonRenderer = ({ item, buildIdFunction, tooltip, - color, + color = 'default', onDelete, -}) => { +}: Props): ((args: { data: Invitation | ItemMembership }) => JSX.Element) => { // todo: use typescript to precise data is one of Invitation or Membership const ChildComponent = ({ data }) => { const isFromParent = useIsParentInstance({ @@ -52,26 +61,7 @@ const TableRowDeleteButtonRenderer = ({ return renderButton(); }; - ChildComponent.propTypes = { - data: PropTypes.shape({ - id: PropTypes.string.isRequired, - itemPath: PropTypes.string.isRequired, - }).isRequired, - }; return ChildComponent; }; -TableRowDeleteButtonRenderer.propTypes = { - disabled: PropTypes.bool, - tooltip: PropTypes.string, - onClick: PropTypes.func.isRequired, - color: PropTypes.string, -}; - -TableRowDeleteButtonRenderer.defaultProps = { - tooltip: null, - color: 'default', - disabled: false, -}; - export default TableRowDeleteButtonRenderer; diff --git a/src/components/item/sharing/TableRowPermissionRenderer.js b/src/components/item/sharing/TableRowPermissionRenderer.tsx similarity index 64% rename from src/components/item/sharing/TableRowPermissionRenderer.js rename to src/components/item/sharing/TableRowPermissionRenderer.tsx index 62b925cbc..24dd9c9b0 100644 --- a/src/components/item/sharing/TableRowPermissionRenderer.js +++ b/src/components/item/sharing/TableRowPermissionRenderer.tsx @@ -1,12 +1,25 @@ -import PropTypes from 'prop-types'; +import { PermissionLevel } from '@graasp/sdk'; -import { PERMISSION_LEVELS } from '../../../enums'; import { useIsParentInstance } from '../../../utils/item'; import ItemMembershipSelect from './ItemMembershipSelect'; -const TableRowPermissionRenderer = ({ item, editFunction, createFunction }) => { +type Props = { item; editFunction; createFunction }; + +type ChildProps = { + data: { + id: string; + permission: PermissionLevel; + itemPath: string; + }; +}; + +const TableRowPermissionRenderer = ({ + item, + editFunction, + createFunction, +}: Props): ((props: ChildProps) => JSX.Element) => { // todo: use typescript to precise data is one of Invitation or Membership - const ChildComponent = ({ data: instance }) => { + const ChildComponent = ({ data: instance }: ChildProps) => { const isParentMembership = useIsParentInstance({ instance, item, @@ -30,13 +43,7 @@ const TableRowPermissionRenderer = ({ item, editFunction, createFunction }) => { /> ); }; - ChildComponent.propTypes = { - data: PropTypes.shape({ - id: PropTypes.string.isRequired, - permission: PropTypes.oneOf(Object.values(PERMISSION_LEVELS)).isRequired, - itemPath: PropTypes.string.isRequired, - }).isRequired, - }; + return ChildComponent; }; export default TableRowPermissionRenderer; diff --git a/src/components/main/FavoriteItems.tsx b/src/components/main/FavoriteItems.tsx index 9eb2c670a..82a0c8440 100644 --- a/src/components/main/FavoriteItems.tsx +++ b/src/components/main/FavoriteItems.tsx @@ -5,6 +5,7 @@ import Box from '@mui/material/Box'; import { FC, useContext, useEffect } from 'react'; import { MUTATION_KEYS } from '@graasp/query-client'; +import { ItemRecord } from '@graasp/query-client/dist/types'; import { Item } from '@graasp/sdk'; import { BUILDER } from '@graasp/translations'; import { Loader } from '@graasp/ui'; @@ -25,8 +26,8 @@ import Main from './Main'; // todo: find other possible solutions // todo: improve types with refactor export const getExistingItems = ( - items: List, -): List => items.filter((item) => !item.statusCode); + items: List, +): List => items.filter((item) => !item.statusCode); // todo: improve types with refactor export const containsNonExistingItems = ( diff --git a/src/components/main/Item.tsx b/src/components/main/Item.tsx index 7184c3f3a..2b9e6f978 100644 --- a/src/components/main/Item.tsx +++ b/src/components/main/Item.tsx @@ -1,14 +1,11 @@ -import { RecordOf } from 'immutable'; +import { List, RecordOf } from 'immutable'; import truncate from 'lodash.truncate'; import { CSSProperties, FC, PropsWithChildren, useContext } from 'react'; import { Link } from 'react-router-dom'; -import { - EmbeddedLinkItemExtra, - Item as GraaspItem, - ItemMembership, -} from '@graasp/sdk'; +import { ItemMembershipRecord } from '@graasp/query-client/dist/types'; +import { EmbeddedLinkItemExtra, Item as GraaspItem } from '@graasp/sdk'; import { Card as GraaspCard, Thumbnail } from '@graasp/ui'; import { DESCRIPTION_MAX_LENGTH } from '../../config/constants'; @@ -42,7 +39,7 @@ const NameWrapper = ({ type Props = { item: RecordOf; - memberships: RecordOf[]; + memberships: List; }; const Item: FC = ({ item, memberships }) => { diff --git a/src/components/main/ItemScreen.js b/src/components/main/ItemScreen.tsx similarity index 87% rename from src/components/main/ItemScreen.js rename to src/components/main/ItemScreen.tsx index 0821fff55..91a663597 100644 --- a/src/components/main/ItemScreen.js +++ b/src/components/main/ItemScreen.tsx @@ -2,7 +2,9 @@ import { useContext, useEffect } from 'react'; import { useParams } from 'react-router'; import { MUTATION_KEYS } from '@graasp/query-client'; +import { ItemType } from '@graasp/sdk'; import { ItemLoginAuthorization } from '@graasp/ui'; +import { SignInPropertiesType } from '@graasp/ui/dist/itemLogin/ItemLoginScreen'; import { ITEM_ACTION_TABS, @@ -16,7 +18,6 @@ import { ITEM_LOGIN_SIGN_IN_PASSWORD_ID, ITEM_LOGIN_SIGN_IN_USERNAME_ID, } from '../../config/selectors'; -import { ITEM_TYPES } from '../../enums'; import { getHighestPermissionForMemberFromMemberships } from '../../utils/membership'; import ErrorAlert from '../common/ErrorAlert'; import { CurrentUserContext } from '../context/CurrentUserContext'; @@ -34,7 +35,7 @@ import Main from './Main'; const { useItem, useCurrentMember, useItemLogin, useItemMemberships } = hooks; -const ItemScreen = () => { +const ItemScreen = (): JSX.Element => { const { itemId } = useParams(); const { data: item, isError } = useItem(itemId); @@ -54,7 +55,7 @@ const ItemScreen = () => { } const itemMembership = getHighestPermissionForMemberFromMemberships({ - memberships: memberships?.toJS(), + memberships, memberId: currentMember?.id, }); const permission = itemMembership?.permission; @@ -67,7 +68,7 @@ const ItemScreen = () => { switch (openedActionTabId) { case ITEM_ACTION_TABS.SHARING: { - return ; + return ; } case ITEM_ACTION_TABS.DASHBOARD: { return ; @@ -89,18 +90,20 @@ const ItemScreen = () => { return (
- {item.type === ITEM_TYPES.FOLDER && } + {item.type === ItemType.FOLDER && } {content}
); }; -const WrappedItemScreen = () => { +const WrappedItemScreen = (): JSX.Element => { const { mutate: signOut } = useMutation(MUTATION_KEYS.SIGN_OUT); - const { mutate: itemLoginSignIn } = useMutation( - MUTATION_KEYS.POST_ITEM_LOGIN, - ); + const { mutate: itemLoginSignIn } = useMutation< + unknown, + unknown, + { itemId: string } & SignInPropertiesType + >(MUTATION_KEYS.POST_ITEM_LOGIN); const { itemId } = useParams(); const ForbiddenContent = ; @@ -118,7 +121,7 @@ const WrappedItemScreen = () => { signInButtonId: ITEM_LOGIN_SIGN_IN_BUTTON_ID, passwordInputId: ITEM_LOGIN_SIGN_IN_PASSWORD_ID, modeSelectId: ITEM_LOGIN_SIGN_IN_MODE_ID, - })(ItemScreen); + })(ItemScreen as any); // todo: improve type return ; }; diff --git a/src/components/main/Items.js b/src/components/main/Items.tsx similarity index 63% rename from src/components/main/Items.js rename to src/components/main/Items.tsx index b41b048b9..85f604283 100644 --- a/src/components/main/Items.js +++ b/src/components/main/Items.tsx @@ -1,8 +1,8 @@ import { List } from 'immutable'; -import PropTypes from 'prop-types'; import { useContext } from 'react'; +import { ItemRecord } from '@graasp/query-client/dist/types'; import { Loader } from '@graasp/ui'; import { hooks } from '../../config/queryClient'; @@ -14,27 +14,53 @@ import ItemsTable from './ItemsTable'; const { useManyItemMemberships } = hooks; +type Props = { + items: List; + title: string; + id?: string; + headerElements?: JSX.Element[]; + // eslint-disable-next-line react/forbid-prop-types + actions?: any; + ToolbarActions?: React.FC<{ + selectedIds: string[]; + }>; + clickable?: boolean; + defaultSortedColumn?: { + updatedAt?: 'desc' | 'asc' | null; + createdAt?: 'desc' | 'asc' | null; + type?: 'desc' | 'asc' | null; + name?: 'desc' | 'asc' | null; + }; + isEditing?: boolean; + parentId?: string; + showThumbnails?: boolean; + showCreator?: boolean; + enableMemberships?: boolean; +}; + const Items = ({ - items, - title, - id, - headerElements, actions, - ToolbarActions, - clickable, + clickable = true, defaultSortedColumn, - isEditing, + enableMemberships = true, + headerElements = [], + id, + isEditing = false, + items, parentId, - showThumbnails, - showCreator, - enableMemberships, -}) => { + showCreator = false, + showThumbnails = true, + title, + ToolbarActions, +}: Props): JSX.Element => { const { mode } = useContext(LayoutContext); const itemSearch = useItemSearch(items); const { data: memberships, isLoading: isMembershipsLoading } = useManyItemMemberships( enableMemberships - ? itemSearch?.results?.map(({ id: itemId }) => itemId).toJS() + ? (itemSearch?.results + ?.map(({ id: itemId }) => itemId) + .toJS() as string[]) : [], ); // todo: disable depending on showCreator @@ -50,15 +76,13 @@ const Items = ({ case ITEM_LAYOUT_MODES.GRID: return ( ); @@ -71,7 +95,7 @@ const Items = ({ tableTitle={title} defaultSortedColumn={defaultSortedColumn} items={itemSearch.results} - memberships={memberships} + membershipLists={memberships} headerElements={[itemSearch.input, ...headerElements]} isSearching={Boolean(itemSearch.text)} ToolbarActions={ToolbarActions} @@ -85,40 +109,4 @@ const Items = ({ } }; -Items.propTypes = { - items: PropTypes.instanceOf(List).isRequired, - title: PropTypes.string.isRequired, - id: PropTypes.string, - headerElements: PropTypes.arrayOf(PropTypes.element), - // eslint-disable-next-line react/forbid-prop-types - actions: PropTypes.any, - ToolbarActions: PropTypes.func, - clickable: PropTypes.bool, - defaultSortedColumn: PropTypes.shape({ - updatedAt: PropTypes.string, - createdAt: PropTypes.string, - type: PropTypes.string, - name: PropTypes.string, - }), - isEditing: PropTypes.bool, - parentId: PropTypes.string, - showThumbnails: PropTypes.bool, - showCreator: PropTypes.bool, - enableMemberships: PropTypes.bool, -}; - -Items.defaultProps = { - id: null, - headerElements: [], - actions: null, - ToolbarActions: null, - clickable: true, - defaultSortedColumn: {}, - isEditing: false, - parentId: null, - showThumbnails: true, - showCreator: false, - enableMemberships: true, -}; - export default Items; diff --git a/src/components/main/ItemsGrid.js b/src/components/main/ItemsGrid.tsx similarity index 82% rename from src/components/main/ItemsGrid.js rename to src/components/main/ItemsGrid.tsx index 3b43d26fb..16ae3fb5b 100644 --- a/src/components/main/ItemsGrid.js +++ b/src/components/main/ItemsGrid.tsx @@ -1,5 +1,4 @@ import { List } from 'immutable'; -import PropTypes from 'prop-types'; import { Box, Typography, styled } from '@mui/material'; import Grid from '@mui/material/Grid'; @@ -9,6 +8,10 @@ import Select from '@mui/material/Select'; import { useState } from 'react'; +import { + ItemMembershipRecord, + ItemRecord, +} from '@graasp/query-client/dist/types'; import { BUILDER } from '@graasp/translations'; import { GRID_ITEMS_PER_PAGE_CHOICES } from '../../config/constants'; @@ -31,15 +34,28 @@ const StyledBox = styled(Box)(({ theme }) => ({ alignItems: 'center', })); -const ItemsGrid = (props) => { +type Props = { + items: List; + membershipLists: List>; + title: string; + itemSearch?: { + text?: string; + // input: PropTypes.instanceOf(ItemSearchInput), + }; + headerElements: JSX.Element[]; + parentId?: string; + isEditing?: boolean; +}; + +const ItemsGrid = (props: Props): JSX.Element => { const { items = List(), title, itemSearch, - headerElements, - memberships, - isEditing, + membershipLists, parentId, + headerElements = [], + isEditing = false, } = props; const { t: translateBuilder } = useBuilderTranslation(); @@ -76,7 +92,7 @@ const ItemsGrid = (props) => { )); @@ -98,7 +114,7 @@ const ItemsGrid = (props) => { labelId={ITEMS_GRID_ITEMS_PER_PAGE_SELECT_LABEL_ID} id={ITEMS_GRID_ITEMS_PER_PAGE_SELECT_ID} value={itemsPerPage} - onChange={(e) => setItemsPerPage(e.target.value)} + onChange={(e) => setItemsPerPage(e.target.value as number)} label={translateBuilder(BUILDER.ITEMS_GRID_ITEMS_PER_PAGE_TITLE)} > {GRID_ITEMS_PER_PAGE_CHOICES.map((v) => ( @@ -109,36 +125,14 @@ const ItemsGrid = (props) => { setPage(v)} + onChange={(_e, v) => setPage(v)} /> ); }; -ItemsGrid.propTypes = { - items: PropTypes.instanceOf(List).isRequired, - memberships: PropTypes.instanceOf(List).isRequired, - title: PropTypes.string.isRequired, - itemSearch: PropTypes.shape({ - text: PropTypes.string, - // input: PropTypes.instanceOf(ItemSearchInput), - }), - headerElements: PropTypes.arrayOf(PropTypes.element), - parentId: PropTypes.string, - isEditing: PropTypes.bool, -}; - -ItemsGrid.defaultProps = { - itemSearch: null, - headerElements: [], - isEditing: false, - parentId: null, -}; - export default ItemsGrid; diff --git a/src/components/main/ItemsTable.tsx b/src/components/main/ItemsTable.tsx index 14691a768..63c384c74 100644 --- a/src/components/main/ItemsTable.tsx +++ b/src/components/main/ItemsTable.tsx @@ -5,10 +5,13 @@ import { FC, useCallback, useContext, useMemo } from 'react'; import { useNavigate, useParams } from 'react-router'; import { MUTATION_KEYS } from '@graasp/query-client'; +import { + ItemMembershipRecord, + ItemRecord, +} from '@graasp/query-client/dist/types'; import { FolderItemExtra, Item, - ItemMembership, ItemType, Member, ShortcutItemExtra, @@ -38,8 +41,8 @@ import ItemsToolbar from './ItemsToolbar'; const { useItem } = hooks; type Props = { - items: List>; - memberships: List>; + items: List; + membershipLists: List>; tableTitle: string; id?: string; headerElements: JSX.Element[]; @@ -50,10 +53,10 @@ type Props = { }>; clickable?: boolean; defaultSortedColumn?: { - updatedAt: 'desc' | 'asc' | null | undefined; - createdAt: 'desc' | 'asc' | null | undefined; - type: 'desc' | 'asc' | null | undefined; - name: 'desc' | 'asc' | null | undefined; + updatedAt?: 'desc' | 'asc' | null; + createdAt?: 'desc' | 'asc' | null; + type?: 'desc' | 'asc' | null; + name?: 'desc' | 'asc' | null; }; isEditing?: boolean; showThumbnails?: boolean; @@ -65,7 +68,7 @@ const ItemsTable: FC = ({ tableTitle, id: tableId = '', items: rows = List(), - memberships = List(), + membershipLists = List(), headerElements = [], isSearching = false, actions, @@ -161,7 +164,7 @@ const ItemsTable: FC = ({ translateBuilder(BUILDER.ITEMS_TABLE_DRAG_DEFAULT_MESSAGE); const ActionComponent = ActionsCellRenderer({ - memberships, + membershipLists, items: rows, member, }); @@ -211,6 +214,8 @@ const ItemsTable: FC = ({ textAlign: 'right', }, sortable: false, + // prevent ellipsis for small screens + minWidth: 165, }, ]; diff --git a/src/components/main/ItemsToolbar.js b/src/components/main/ItemsToolbar.tsx similarity index 56% rename from src/components/main/ItemsToolbar.js rename to src/components/main/ItemsToolbar.tsx index 242d658c3..120df8007 100644 --- a/src/components/main/ItemsToolbar.js +++ b/src/components/main/ItemsToolbar.tsx @@ -1,8 +1,11 @@ -import PropTypes from 'prop-types'; - import { Grid, Typography } from '@mui/material'; -const ItemsToolbar = ({ title, headerElements }) => ( +type Props = { + title: string; + headerElements?: JSX.Element[]; +}; + +const ItemsToolbar = ({ title, headerElements }: Props): JSX.Element => ( @@ -15,13 +18,4 @@ const ItemsToolbar = ({ title, headerElements }) => ( ); -ItemsToolbar.propTypes = { - title: PropTypes.string.isRequired, - headerElements: PropTypes.arrayOf(PropTypes.element), -}; - -ItemsToolbar.defaultProps = { - headerElements: null, -}; - export default ItemsToolbar; diff --git a/src/components/main/Main.tsx b/src/components/main/Main.tsx index 7ad15bbbc..abb29c570 100644 --- a/src/components/main/Main.tsx +++ b/src/components/main/Main.tsx @@ -34,7 +34,7 @@ const StyledLink = styled(Link)(() => ({ type Props = { children: JSX.Element | (JSX.Element & string) }; const Main: FC = ({ children }) => { - const { isMainMenuOpen } = useContext(LayoutContext); + const { isMainMenuOpen, setIsMainMenuOpen } = useContext(LayoutContext); const { data: currentMember } = hooks.useCurrentMember(); const memberId = currentMember?.get('id'); // mutations to handle the mentions @@ -94,6 +94,12 @@ const Main: FC = ({ children }) => { return ( { + setIsMainMenuOpen(true); + }} + handleDrawerClose={() => { + setIsMainMenuOpen(false); + }} headerId={HEADER_APP_BAR_ID} headerLeftContent={leftContent} headerRightContent={rightContent} diff --git a/src/components/main/TableHead.tsx b/src/components/main/TableHead.tsx index 751378113..51ff4db3b 100644 --- a/src/components/main/TableHead.tsx +++ b/src/components/main/TableHead.tsx @@ -9,7 +9,7 @@ import { FC } from 'react'; import { BUILDER } from '@graasp/translations'; import { useBuilderTranslation } from '../../config/i18n'; -import { ORDERING } from '../../enums'; +import { Ordering } from '../../enums'; type Props = { classes: { @@ -68,7 +68,7 @@ const CustomTableHead: FC = (props) => { {headCell.label} {orderBy === headCell.id ? ( - {order === ORDERING.DESC + {order === Ordering.DESC ? translateBuilder(BUILDER.TABLE_DESC_SORT_LABEL) : translateBuilder(BUILDER.TABLE_ASC_SORT_LABEL)} diff --git a/src/components/main/TreeModal.js b/src/components/main/TreeModal.tsx similarity index 84% rename from src/components/main/TreeModal.js rename to src/components/main/TreeModal.tsx index 4a0c764d3..c5d692d0d 100644 --- a/src/components/main/TreeModal.js +++ b/src/components/main/TreeModal.tsx @@ -1,5 +1,3 @@ -import PropTypes, { arrayOf } from 'prop-types'; - import Dialog from '@mui/material/Dialog'; import DialogActions from '@mui/material/DialogActions'; import DialogContent from '@mui/material/DialogContent'; @@ -7,6 +5,7 @@ import DialogTitle from '@mui/material/DialogTitle'; import { useState } from 'react'; +import { ItemType } from '@graasp/sdk'; import { BUILDER } from '@graasp/translations'; import { Button, DynamicTreeView, Loader } from '@graasp/ui'; @@ -19,14 +18,31 @@ import { TREE_MODAL_SHARED_ITEMS_ID, buildTreeItemId, } from '../../config/selectors'; -import { ITEM_TYPES, TREE_PREVENT_SELECTION } from '../../enums'; +import { TREE_PREVENT_SELECTION } from '../../enums'; import { getParentsIdsFromPath } from '../../utils/item'; import CancelButton from '../common/CancelButton'; const dialogId = 'simple-dialog-title'; const { useItem, useItems, useOwnItems, useChildren, useSharedItems } = hooks; -const TreeModal = ({ itemIds, open, title, onClose, onConfirm, prevent }) => { +type Props = { + onConfirm: (args: { ids: string[]; to: string }) => void; + onClose: (ags: { id: null | string; open: boolean }) => void; + title: string; + prevent?: string; // TREE_PREVENT_SELECTION + itemIds?: string[]; + open: boolean; +}; + +const TreeModal = ({ + itemIds, + title, + onClose, + onConfirm, + prevent = TREE_PREVENT_SELECTION.NONE, + + open = false, +}: Props): JSX.Element => { const { t: translateBuilder } = useBuilderTranslation(); const { data: ownItems, isLoading: isOwnItemsLoading } = useOwnItems(); // todo: get only shared items with write/admin rights @@ -34,7 +50,7 @@ const TreeModal = ({ itemIds, open, title, onClose, onConfirm, prevent }) => { const { data: sharedItems, isLoading: isSharedItemsLoading } = useSharedItems(); const [selectedId, setSelectedId] = useState(null); - const { data: items, isItemLoading } = useItems(itemIds); + const { data: items, isLoading: isItemLoading } = useItems(itemIds); // build the expanded item ids list for a given tree (with treeRootId as id) // by default, we expand all parents of items @@ -93,7 +109,7 @@ const TreeModal = ({ itemIds, open, title, onClose, onConfirm, prevent }) => { }; const onClickConfirm = () => { - onConfirm({ id: itemIds, to: selectedId }); + onConfirm({ ids: itemIds, to: selectedId }); handleClose(); }; @@ -105,7 +121,7 @@ const TreeModal = ({ itemIds, open, title, onClose, onConfirm, prevent }) => { } }; - const isFolder = (i) => i.type === ITEM_TYPES.FOLDER; + const isFolder = (i) => i.type === ItemType.FOLDER; // compute tree only when the modal is open const tree = !open ? null : ( @@ -125,11 +141,11 @@ const TreeModal = ({ itemIds, open, title, onClose, onConfirm, prevent }) => { showCheckbox rootLabel={translateBuilder(BUILDER.ITEMS_TREE_OWN_ITEMS_LABEL)} rootId={TREE_MODAL_MY_ITEMS_ID} - rootClassName={buildTreeItemId(TREE_MODAL_MY_ITEMS_ID)} showItemFilter={isFolder} shouldFetchChildrenForItem={isFolder} isTreeItemDisabled={isTreeItemDisabled} - buildTreeItemId={buildTreeItemId} + // todo: change graasp-ui + buildTreeItemClass={buildTreeItemId as any} /> { showCheckbox rootLabel={translateBuilder(BUILDER.NAVIGATION_SHARED_ITEMS_TITLE)} rootId={TREE_MODAL_SHARED_ITEMS_ID} - rootClassName={buildTreeItemId(TREE_MODAL_SHARED_ITEMS_ID)} showItemFilter={isFolder} shouldFetchChildrenForItem={isFolder} isTreeItemDisabled={isTreeItemDisabled} - buildTreeItemId={buildTreeItemId} + // todo: change graasp-ui + buildTreeItemClass={buildTreeItemId as any} /> ); @@ -178,19 +194,4 @@ const TreeModal = ({ itemIds, open, title, onClose, onConfirm, prevent }) => { ); }; -TreeModal.propTypes = { - onConfirm: PropTypes.func.isRequired, - onClose: PropTypes.func.isRequired, - title: PropTypes.string.isRequired, - prevent: PropTypes.oneOf(Object.values(TREE_PREVENT_SELECTION)), - itemIds: arrayOf(PropTypes.string), - open: PropTypes.bool, -}; - -TreeModal.defaultProps = { - prevent: TREE_PREVENT_SELECTION.NONE, - itemIds: null, - open: false, -}; - export default TreeModal; diff --git a/src/components/member/DeleteMemberDialog.js b/src/components/member/DeleteMemberDialog.tsx similarity index 92% rename from src/components/member/DeleteMemberDialog.js rename to src/components/member/DeleteMemberDialog.tsx index 4225530f8..bd89f324d 100644 --- a/src/components/member/DeleteMemberDialog.js +++ b/src/components/member/DeleteMemberDialog.tsx @@ -1,5 +1,3 @@ -import PropTypes from 'prop-types'; - import { Grid, Typography } from '@mui/material'; import Dialog from '@mui/material/Dialog'; import DialogActions from '@mui/material/DialogActions'; @@ -21,10 +19,18 @@ import { } from '../../config/selectors'; import CancelButton from '../common/CancelButton'; -const DeleteMemberDialog = ({ id }) => { +type Props = { + id: string; +}; + +const DeleteMemberDialog = ({ id }: Props): JSX.Element => { const { t: translateAccount } = useAccountTranslation(); const [open, setOpen] = useState(false); - const { mutate: deleteMember } = useMutation(MUTATION_KEYS.DELETE_MEMBER); + const { mutate: deleteMember } = useMutation< + unknown, + unknown, + { id: string } + >(MUTATION_KEYS.DELETE_MEMBER); const alertDialogTitle = 'alert-dialog-title'; const alertDialogDescription = 'alert-dialog-description'; @@ -87,7 +93,6 @@ const DeleteMemberDialog = ({ id }) => { id={DELETE_MEMBER_BUTTON_ID} variant="contained" color="error" - my={1} onClick={() => setOpen(true)} > {translateAccount(ACCOUNT.PROFILE_DELETE_ACCOUNT_BUTTON)} @@ -102,8 +107,4 @@ const DeleteMemberDialog = ({ id }) => { ); }; -DeleteMemberDialog.propTypes = { - id: PropTypes.string.isRequired, -}; - export default DeleteMemberDialog; diff --git a/src/components/member/MemberProfileScreen.js b/src/components/member/MemberProfileScreen.tsx similarity index 89% rename from src/components/member/MemberProfileScreen.js rename to src/components/member/MemberProfileScreen.tsx index af273188f..f45d382d2 100644 --- a/src/components/member/MemberProfileScreen.js +++ b/src/components/member/MemberProfileScreen.tsx @@ -2,7 +2,9 @@ import FileCopyIcon from '@mui/icons-material/FileCopy'; import { Box, Grid, IconButton, Typography } from '@mui/material'; import { useContext } from 'react'; +import { QueryObserverResult } from 'react-query'; +import { MemberRecord } from '@graasp/query-client/dist/types'; import { ACCOUNT, COMMON } from '@graasp/translations'; import { Loader } from '@graasp/ui'; @@ -32,10 +34,11 @@ import EmailPreferenceSwitch from './EmailPreferenceSwitch'; import LanguageSwitch from './LanguageSwitch'; import PasswordSetting from './PasswordSetting'; -const MemberProfileScreen = () => { +const MemberProfileScreen = (): JSX.Element => { const { t } = useAccountTranslation(); const { t: translateCommon } = useCommonTranslation(); - const { data: member, isLoading } = useContext(CurrentUserContext); + const { data: member, isLoading } = + useContext>(CurrentUserContext); if (isLoading) { return ; @@ -108,7 +111,7 @@ const MemberProfileScreen = () => {
@@ -122,7 +125,10 @@ const MemberProfileScreen = () => {
@@ -130,7 +136,7 @@ const MemberProfileScreen = () => { - + diff --git a/src/components/table/ActionsCellRenderer.js b/src/components/table/ActionsCellRenderer.tsx similarity index 58% rename from src/components/table/ActionsCellRenderer.js rename to src/components/table/ActionsCellRenderer.tsx index c1f42b474..b49c98212 100644 --- a/src/components/table/ActionsCellRenderer.js +++ b/src/components/table/ActionsCellRenderer.tsx @@ -1,8 +1,13 @@ -import { List, Record } from 'immutable'; -import PropTypes from 'prop-types'; +import { List } from 'immutable'; import { useEffect, useState } from 'react'; +import { + ItemMembershipRecord, + ItemRecord, + MemberRecord, +} from '@graasp/query-client/dist/types'; + import { getMembershipsForItem, isItemUpdateAllowedForUser, @@ -11,22 +16,43 @@ import EditButton from '../common/EditButton'; import DownloadButton from '../main/DownloadButton'; import ItemMenu from '../main/ItemMenu'; +type Props = { + membershipLists: List>; + items: List; + member: MemberRecord; +}; + +type ChildProps = { data: ItemRecord }; + // items and memberships match by index -const ActionsCellRenderer = ({ memberships, items, member }) => { - const ChildComponent = ({ data: item }) => { +const ActionsCellRenderer = ({ + membershipLists, + items, + member, +}: Props): ((props: ChildProps) => JSX.Element) => { + const ChildComponent = ({ data: item }: ChildProps) => { const [canEdit, setCanEdit] = useState(false); useEffect(() => { - if (items && memberships && !memberships.isEmpty() && !items.isEmpty()) { + if ( + items && + membershipLists && + !membershipLists.isEmpty() && + !items.isEmpty() + ) { setCanEdit( isItemUpdateAllowedForUser({ - memberships: getMembershipsForItem({ item, items, memberships }), + memberships: getMembershipsForItem({ + item, + items, + membershipLists, + }), memberId: member?.id, }), ); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [items, memberships, item, member]); + }, [items, membershipLists, item, member]); const renderAnyoneActions = () => ( @@ -52,18 +78,8 @@ const ActionsCellRenderer = ({ memberships, items, member }) => { ); }; - ChildComponent.propTypes = { - data: PropTypes.shape({ - id: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - }).isRequired, - }; - return ChildComponent; -}; -ActionsCellRenderer.propTypes = { - memberships: PropTypes.instanceOf(List).isRequired, - member: PropTypes.instanceOf(Record).isRequired, + return ChildComponent; }; export default ActionsCellRenderer; diff --git a/src/components/table/ItemNameCellRenderer.js b/src/components/table/ItemNameCellRenderer.tsx similarity index 60% rename from src/components/table/ItemNameCellRenderer.js rename to src/components/table/ItemNameCellRenderer.tsx index d7aeab494..4dfccadc3 100644 --- a/src/components/table/ItemNameCellRenderer.js +++ b/src/components/table/ItemNameCellRenderer.tsx @@ -1,22 +1,27 @@ -import PropTypes from 'prop-types'; - import { Box, Typography } from '@mui/material'; +import { ItemRecord } from '@graasp/query-client/dist/types'; import { ItemIcon, Thumbnail } from '@graasp/ui'; import { hooks } from '../../config/queryClient'; import { buildNameCellRendererId } from '../../config/selectors'; import { getEmbeddedLinkExtra } from '../../utils/itemExtra'; -const ItemNameCellRenderer = (showThumbnails) => { - const Component = ({ data: item }) => { +type ChildProps = { data: ItemRecord }; + +const ItemNameCellRenderer = ( + showThumbnails: boolean, +): ((props: ChildProps) => JSX.Element) => { + const Component = ({ data: item }: ChildProps): JSX.Element => { + // TODO: improve types + const linkExtra = getEmbeddedLinkExtra(item.extra as any); + const alt = item.name; const defaultValueComponent = ( ); @@ -29,12 +34,12 @@ const ItemNameCellRenderer = (showThumbnails) => { {showThumbnails && ( )} @@ -44,15 +49,6 @@ const ItemNameCellRenderer = (showThumbnails) => { ); }; - Component.propTypes = { - data: PropTypes.shape({ - id: PropTypes.string.isRequired, - extra: PropTypes.shape({}).isRequired, - type: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - }).isRequired, - }; - return Component; }; diff --git a/src/components/table/MemberNameCellRenderer.js b/src/components/table/MemberNameCellRenderer.js deleted file mode 100644 index fb2076cfa..000000000 --- a/src/components/table/MemberNameCellRenderer.js +++ /dev/null @@ -1,20 +0,0 @@ -import PropTypes from 'prop-types'; - -import { Typography } from '@mui/material'; - -const MemberNameCellRenderer = ({ users, defaultValue }) => { - const ChildComponent = ({ data: item }) => { - // users might contain null users - const user = users?.find(({ id }) => id === item.creator); - - return {user?.name ?? defaultValue}; - }; - ChildComponent.propTypes = { - data: PropTypes.shape({ - creator: PropTypes.string.isRequired, - }).isRequired, - }; - return ChildComponent; -}; - -export default MemberNameCellRenderer; diff --git a/src/components/table/MemberNameCellRenderer.tsx b/src/components/table/MemberNameCellRenderer.tsx new file mode 100644 index 000000000..99ab09951 --- /dev/null +++ b/src/components/table/MemberNameCellRenderer.tsx @@ -0,0 +1,22 @@ +import { Typography } from '@mui/material'; + +import { ItemRecord } from '@graasp/query-client/dist/types'; + +type Props = { users; defaultValue }; +type ChildProps = { data: ItemRecord }; + +const MemberNameCellRenderer = ({ + users, + defaultValue, +}: Props): ((childProps: ChildProps) => JSX.Element) => { + const ChildComponent = ({ data: item }: ChildProps): JSX.Element => { + // users might contain null users + const user = users?.find(({ id }) => id === item.creator); + + return {user?.name ?? defaultValue}; + }; + + return ChildComponent; +}; + +export default MemberNameCellRenderer; diff --git a/src/config/constants.ts b/src/config/constants.ts index 3e1ffb9c4..6e350154c 100644 --- a/src/config/constants.ts +++ b/src/config/constants.ts @@ -1,6 +1,11 @@ -import { Context, buildSignInPath } from '@graasp/sdk'; +import { + Context, + ItemType, + PermissionLevel, + buildSignInPath, +} from '@graasp/sdk'; -import { ITEM_LAYOUT_MODES, ITEM_TYPES, PERMISSION_LEVELS } from '../enums'; +import { ITEM_LAYOUT_MODES } from '../enums'; export const APP_NAME = 'Graasp'; @@ -59,7 +64,7 @@ export const MIME_TYPES = { AUDIO: ['audio/mpeg', 'audio/mp3'], PDF: ['application/pdf'], }; -export const DRAWER_WIDTH = 300; +export const DRAWER_WIDTH = 240; export const DEFAULT_LOCALE = 'en-US'; export const DEFAULT_LANG = 'en'; export const DEFAULT_EMAIL_FREQUENCY = 'always'; @@ -76,13 +81,13 @@ export const DEFAULT_PINNED_SETTING = false; export const DEFAULT_COLLAPSIBLE_SETTING = false; export const DEFAULT_RESIZE_SETTING = false; -export const DEFAULT_PERMISSION_LEVEL = PERMISSION_LEVELS.READ; +export const DEFAULT_PERMISSION_LEVEL = PermissionLevel.Read; export const DEFAULT_ANALYZER_HEIGHT = 2300; export const PERMISSIONS_EDITION_ALLOWED = [ - PERMISSION_LEVELS.WRITE, - PERMISSION_LEVELS.ADMIN, + PermissionLevel.Write, + PermissionLevel.Admin, ]; export const DEFAULT_ITEM_LAYOUT_MODE = ITEM_LAYOUT_MODES.LIST; @@ -134,12 +139,12 @@ export const SETTINGS_ITEM_LOGIN_SIGN_IN_MODE_DEFAULT = export const USER_ITEM_ORDER = 'user_order'; export const ITEM_TYPES_WITH_CAPTIONS = [ - ITEM_TYPES.FOLDER, - ITEM_TYPES.S3_FILE, - ITEM_TYPES.FILE, - ITEM_TYPES.APP, - ITEM_TYPES.LINK, - ITEM_TYPES.DOCUMENT, + ItemType.FOLDER, + ItemType.S3_FILE, + ItemType.LOCAL_FILE, + ItemType.APP, + ItemType.LINK, + ItemType.DOCUMENT, ]; export const MIN_SCREEN_WIDTH = 1000; diff --git a/src/enums/chatbox.js b/src/enums/chatbox.js deleted file mode 100644 index b4e4827ba..000000000 --- a/src/enums/chatbox.js +++ /dev/null @@ -1,8 +0,0 @@ -const CHAT_STATUS = { - OPEN: 'true', - CLOSE: 'false', -}; - -Object.freeze(CHAT_STATUS); - -export default CHAT_STATUS; diff --git a/src/enums/chatbox.ts b/src/enums/chatbox.ts new file mode 100644 index 000000000..0eeb14afd --- /dev/null +++ b/src/enums/chatbox.ts @@ -0,0 +1,6 @@ +enum ChatStatus { + OPEN = 'true', + CLOSE = 'false', +} + +export default ChatStatus; diff --git a/src/enums/index.js b/src/enums/index.js deleted file mode 100644 index dad70faf2..000000000 --- a/src/enums/index.js +++ /dev/null @@ -1,19 +0,0 @@ -import CHAT_STATUS from './chatbox'; -import ITEM_DATA_TYPES from './itemDataTypes'; -import ITEM_LAYOUT_MODES from './itemLayoutModes'; -import ITEM_TYPES from './itemTypes'; -import MIME_TYPES from './mimeTypes'; -import ORDERING from './orderingTypes'; -import PERMISSION_LEVELS from './permissionLevels'; -import TREE_PREVENT_SELECTION from './treePreventSelection'; - -export { - ITEM_LAYOUT_MODES, - ITEM_DATA_TYPES, - ITEM_TYPES, - MIME_TYPES, - ORDERING, - PERMISSION_LEVELS, - TREE_PREVENT_SELECTION, - CHAT_STATUS, -}; diff --git a/src/enums/index.ts b/src/enums/index.ts new file mode 100644 index 000000000..61e75f4db --- /dev/null +++ b/src/enums/index.ts @@ -0,0 +1,13 @@ +import ChatStatus from './chatbox'; +import ITEM_LAYOUT_MODES from './itemLayoutModes'; +import MIME_TYPES from './mimeTypes'; +import Ordering from './orderingTypes'; +import TREE_PREVENT_SELECTION from './treePreventSelection'; + +export { + ITEM_LAYOUT_MODES, + MIME_TYPES, + Ordering, + TREE_PREVENT_SELECTION, + ChatStatus, +}; diff --git a/src/enums/itemDataTypes.js b/src/enums/itemDataTypes.js deleted file mode 100644 index 9ea4c57aa..000000000 --- a/src/enums/itemDataTypes.js +++ /dev/null @@ -1,7 +0,0 @@ -const ITEM_DATA_TYPES = { - DATE: 'date', -}; - -Object.freeze(ITEM_DATA_TYPES); - -export default ITEM_DATA_TYPES; diff --git a/src/enums/itemLayoutModes.js b/src/enums/itemLayoutModes.ts similarity index 74% rename from src/enums/itemLayoutModes.js rename to src/enums/itemLayoutModes.ts index d974d0fa7..8197915f9 100644 --- a/src/enums/itemLayoutModes.js +++ b/src/enums/itemLayoutModes.ts @@ -1,3 +1,4 @@ +// cannot move to ts because of cypress files const ITEM_LAYOUT_MODES = { GRID: 'grid', LIST: 'list', diff --git a/src/enums/mimeTypes.js b/src/enums/mimeTypes.ts similarity index 100% rename from src/enums/mimeTypes.js rename to src/enums/mimeTypes.ts diff --git a/src/enums/orderingTypes.js b/src/enums/orderingTypes.js deleted file mode 100644 index e3f864644..000000000 --- a/src/enums/orderingTypes.js +++ /dev/null @@ -1,8 +0,0 @@ -const ORDERING = { - ASC: 'asc', - DESC: 'desc', -}; - -Object.freeze(ORDERING); - -export default ORDERING; diff --git a/src/enums/orderingTypes.ts b/src/enums/orderingTypes.ts new file mode 100644 index 000000000..65dbafb77 --- /dev/null +++ b/src/enums/orderingTypes.ts @@ -0,0 +1,6 @@ +enum Ordering { + ASC = 'asc', + DESC = 'desc', +} + +export default Ordering; diff --git a/src/enums/permissionLevels.js b/src/enums/permissionLevels.js deleted file mode 100644 index 32ebde0f6..000000000 --- a/src/enums/permissionLevels.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * @deprecated use graasp sdk - */ -const PERMISSION_LEVELS = { - READ: 'read', - WRITE: 'write', - ADMIN: 'admin', -}; - -Object.freeze(PERMISSION_LEVELS); -/** - * @deprecated use graasp sdk - */ -export default PERMISSION_LEVELS; diff --git a/src/enums/treePreventSelection.js b/src/enums/treePreventSelection.ts similarity index 100% rename from src/enums/treePreventSelection.js rename to src/enums/treePreventSelection.ts diff --git a/src/utils/item.ts b/src/utils/item.ts index d3216a939..50441f2d2 100644 --- a/src/utils/item.ts +++ b/src/utils/item.ts @@ -150,7 +150,9 @@ export const useIsParentInstance = ({ instance, item, }: { - instance: Invitation | ItemMembership; + instance: + | Pick, 'itemPath'> + | Pick, 'itemPath'>; item: Item; }): boolean => { const [isParentMembership, setIsParentMembership] = useState(false); diff --git a/src/utils/membership.js b/src/utils/membership.js deleted file mode 100644 index 2ed05df97..000000000 --- a/src/utils/membership.js +++ /dev/null @@ -1,61 +0,0 @@ -import { PERMISSIONS_EDITION_ALLOWED } from '../config/constants'; -import { PERMISSION_LEVELS } from '../enums'; - -// todo: better check with typescript -export const isError = (memberships) => memberships?.statusCode; - -export const isItemUpdateAllowedForUser = ({ memberships, memberId }) => - Boolean( - memberships?.find( - ({ memberId: mId, permission }) => - mId === memberId && PERMISSIONS_EDITION_ALLOWED.includes(permission), - ), - ); - -// get highest permission a member have over an item, -// longer the itemPath, deeper is the permission, thus highested -export const getHighestPermissionForMemberFromMemberships = ({ - memberships, - memberId, -}) => { - const itemMemberships = memberships?.filter( - ({ memberId: mId }) => mId === memberId, - ); - if (!itemMemberships) { - return null; - } - - const sorted = itemMemberships?.sort( - (a, b) => a.itemPath.length > b.itemPath.length, - ); - - return sorted[0]; -}; - -export const isSettingsEditionAllowedForUser = ({ memberships, memberId }) => - memberships?.find( - ({ memberId: mId, permission }) => - mId === memberId && PERMISSION_LEVELS.ADMIN === permission, - ); - -export const membershipsWithoutUser = (memberships, userId) => - memberships?.filter(({ memberId }) => memberId !== userId); - -// util function to get the first membership from useMemberships -// this is necessary to detect errors -export const getMembership = (memberships) => { - if (isError(memberships?.get(0))) { - return undefined; - } - - return memberships?.get(0); -}; - -export const getMembershipsForItem = ({ item, memberships, items }) => { - const index = items.findKey(({ id }) => id === item.id); - const m = memberships?.get(index); - if (isError(m)) { - return undefined; - } - return m; -}; diff --git a/src/utils/membership.ts b/src/utils/membership.ts new file mode 100644 index 000000000..7938577ed --- /dev/null +++ b/src/utils/membership.ts @@ -0,0 +1,96 @@ +import { List } from 'immutable'; + +import { ItemMembershipRecord } from '@graasp/query-client/dist/types'; +import { PermissionLevel } from '@graasp/sdk'; +import { ItemRecord } from '@graasp/ui/dist/types'; + +import { PERMISSIONS_EDITION_ALLOWED } from '../config/constants'; + +// todo: better check with typescript +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export const isError = (membership: any): boolean => + Boolean(membership?.statusCode); + +export const isItemUpdateAllowedForUser = ({ + memberships, + memberId, +}: { + memberships: List; + memberId: string; +}): boolean => + Boolean( + memberships?.find( + ({ memberId: mId, permission }) => + mId === memberId && PERMISSIONS_EDITION_ALLOWED.includes(permission), + ), + ); + +// get highest permission a member have over an item, +// longer the itemPath, deeper is the permission, thus highested +export const getHighestPermissionForMemberFromMemberships = ({ + memberships, + memberId, +}: { + memberships: List; + memberId: string; +}): ItemMembershipRecord => { + const itemMemberships = memberships?.filter( + ({ memberId: mId }) => mId === memberId, + ); + if (!itemMemberships) { + return null; + } + + const sorted = itemMemberships?.sort((a, b) => + a.itemPath.length > b.itemPath.length ? 1 : -1, + ); + + return sorted.first(); +}; + +export const isSettingsEditionAllowedForUser = ({ + memberships, + memberId, +}: { + memberships: List; + memberId: string; +}): boolean => + memberships?.some( + ({ memberId: mId, permission }) => + mId === memberId && PermissionLevel.Admin === permission, + ); + +export const membershipsWithoutUser = ( + memberships: List, + userId: string, +): List => + memberships?.filter(({ memberId }) => memberId !== userId); + +// util function to get the first membership from useMemberships +// this is necessary to detect errors +export const getMembership = ( + memberships: List, +): ItemMembershipRecord => { + if (isError(memberships?.get(0))) { + return undefined; + } + + return memberships?.get(0); +}; + +export const getMembershipsForItem = ({ + item, + membershipLists, + items, +}: { + item: ItemRecord; + items: List; + membershipLists: List>; +}): List | undefined => { + const index = items.findKey(({ id }) => id === item.id); + const m = membershipLists?.get(index); + if (isError(m)) { + return undefined; + } + return m; +}; diff --git a/src/utils/table.js b/src/utils/table.js deleted file mode 100644 index 249a3bec0..000000000 --- a/src/utils/table.js +++ /dev/null @@ -1,65 +0,0 @@ -import { USER_ITEM_ORDER } from '../config/constants'; -import { ORDERING } from '../enums'; - -/** - * Costum sorting function depending on a given property name - * @param {object} a - * @param {object} b - * @param {string} orderBy property name to sort a and b - * @param {string[]} idOrder id array based on user order - */ -export const descendingComparator = (a, b, orderBy, idOrder) => { - if (orderBy === USER_ITEM_ORDER) { - return idOrder.indexOf(a.id) - idOrder.indexOf(b.id); - } - if (b[orderBy] < a[orderBy]) { - return -1; - } - if (b[orderBy] > a[orderBy]) { - return 1; - } - return 0; -}; - -/** - * Return a comparator function depending on the order and the field - * @param {string} order ascending or descending order - * @param {string} orderBy property name used when sorting - * @param {string[]} idOrder id array based on user order - * @returns {function(): number} - */ -export const getComparator = (order, orderBy, idOrder = []) => - order === ORDERING.DESC - ? (a, b) => descendingComparator(a, b, orderBy, idOrder) - : (a, b) => -descendingComparator(a, b, orderBy, idOrder); - -/** - * Returns array sorted given a comparator function - * @param {array} array - * @param {function(): number} comparator - * @returns {array} - */ -export const stableSort = (array, comparator) => { - let stabilizedThis = array.map((el, index) => [el, index]); - stabilizedThis = stabilizedThis.sort((a, b) => { - const order = comparator(a[0], b[0]); - if (order !== 0) return order; - return a[1] - b[1]; - }); - return stabilizedThis.map((el) => el[0]); -}; - -/** - * Returns the correct portion of array given the current page number - * @param {array} table - * @param {number} page - * @param {number} rowsPerPage - * @returns {array} - */ -export const getRowsForPage = (table, { page, rowsPerPage }) => - table.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage); - -export const userOrderComparator = - (userOrder = []) => - (a, b) => - userOrder.indexOf(a.id) - userOrder.indexOf(b.id); diff --git a/yarn.lock b/yarn.lock index 827f69072..5ac94358b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2490,9 +2490,9 @@ __metadata: languageName: node linkType: hard -"@graasp/sdk@npm:0.2.0": - version: 0.2.0 - resolution: "@graasp/sdk@npm:0.2.0" +"@graasp/sdk@npm:0.3.0": + version: 0.3.0 + resolution: "@graasp/sdk@npm:0.3.0" dependencies: "@fastify/secure-session": 5.2.0 aws-sdk: 2.1111.0 @@ -2503,7 +2503,7 @@ __metadata: qs: 6.11.0 slonik: 28.1.1 uuid: 8.3.2 - checksum: 95a76c69dd4577f8a0eb51197d83fdb5c61c58c1c6a7c895e7ca543d4883603bc7deb534fd5b7bc4f61b5f15cd0b6c403bf165e6a6a4a756707f9ad877961339 + checksum: 9204d48d08ebf5b1ed3b37000c8b3398a6f1ae2420ce5a474da0ba115494e14156aee106d3850eb7d892255d668bb0e018efd60359b674fa4b88d195419f77cb languageName: node linkType: hard @@ -2528,6 +2528,44 @@ __metadata: "@graasp/ui@npm:0.7.1": version: 0.7.1 resolution: "@graasp/ui@npm:0.7.1" + dependencies: + "@graasp/sdk": "github:graasp/graasp-sdk.git" + clsx: 1.1.1 + http-status-codes: 2.2.0 + immutable: 4.1.0 + katex: 0.16.0 + lodash.truncate: 4.4.2 + qs: 6.10.5 + react-cookie-consent: 7.4.1 + react-i18next: 11.17.0 + react-quill: 2.0.0-beta.4 + react-rnd: 10.3.7 + react-text-mask: 5.4.3 + uuid: 8.3.2 + peerDependencies: + "@emotion/react": 11.10.4 + "@emotion/styled": 11.10.4 + "@mui/icons-material": 5.8.3 + "@mui/lab": ~5.0.0-alpha.85 + "@mui/material": 5.10.7 + ag-grid-community: 28.1.1 + ag-grid-react: 28.1.1 + i18next: ~21.8.9 + react: 17.0.2 + react-dom: 17.0.2 + react-router-dom: 6.2.2 + peerDependenciesMeta: + ag-grid-community: + optional: true + ag-grid-react: + optional: true + checksum: b09b2adaa1f72b82c35dc86d2bb9fc578336c9b2858fee18302ed82c79046143ac371ce8415ad01a9385ece40e9a8634a53f9aee67d33b2e3752052b59eac5bb + languageName: node + linkType: hard + +"@graasp/ui@npm:0.8.0": + version: 0.8.0 + resolution: "@graasp/ui@npm:0.8.0" dependencies: "@graasp/sdk": 0.2.0 clsx: 1.1.1 @@ -2560,7 +2598,7 @@ __metadata: optional: true ag-grid-react: optional: true - checksum: b09b2adaa1f72b82c35dc86d2bb9fc578336c9b2858fee18302ed82c79046143ac371ce8415ad01a9385ece40e9a8634a53f9aee67d33b2e3752052b59eac5bb + checksum: 800d6b8d6d444c24594685acc0b7d82b5331bdf8be18676e8f26308b45c8ec131531f4953b11b19882d25c0c4dbd2052d5bf3af0236bbf83fe4bf227909609ae languageName: node linkType: hard @@ -10102,9 +10140,9 @@ __metadata: "@emotion/styled": 11.10.5 "@graasp/chatbox": 1.0.1 "@graasp/query-client": 0.1.2 - "@graasp/sdk": 0.2.0 + "@graasp/sdk": 0.3.0 "@graasp/translations": 1.3.0 - "@graasp/ui": 0.7.1 + "@graasp/ui": 0.8.0 "@graasp/websockets": "github:graasp/graasp-websockets.git" "@mui/icons-material": 5.11.0 "@mui/lab": 5.0.0-alpha.117