diff --git a/cypress/e2e/item/bookmarks/bookmarks.cy.ts b/cypress/e2e/item/bookmarks/bookmarks.cy.ts index 95bd4a796..e84f24797 100644 --- a/cypress/e2e/item/bookmarks/bookmarks.cy.ts +++ b/cypress/e2e/item/bookmarks/bookmarks.cy.ts @@ -1,4 +1,6 @@ import { + GuestFactory, + ItemLoginSchemaFactory, PackedFolderItemFactory, PackedItemBookmarkFactory, } from '@graasp/sdk'; @@ -14,6 +16,7 @@ import { BOOKMARK_ICON_SELECTOR, CREATE_ITEM_BUTTON_ID, ITEM_SEARCH_INPUT_ID, + PREVENT_GUEST_MESSAGE_ID, SORTING_ORDERING_SELECTOR_ASC, SORTING_ORDERING_SELECTOR_DESC, SORTING_SELECT_SELECTOR, @@ -38,6 +41,16 @@ const addToBookmark = (itemId: string) => { }; describe('Bookmarked Item', () => { + it('Show message for guest', () => { + const item = PackedFolderItemFactory(); + const guest = GuestFactory({ + itemLoginSchema: ItemLoginSchemaFactory({ item }), + }); + cy.setUpApi({ items: [item], currentMember: guest }); + cy.visit(BOOKMARKED_ITEMS_PATH); + cy.get(`#${PREVENT_GUEST_MESSAGE_ID}`).should('be.visible'); + }); + describe('Member has no bookmarked items', () => { beforeEach(() => { cy.setUpApi({ diff --git a/cypress/e2e/item/home/home.cy.ts b/cypress/e2e/item/home/home.cy.ts index 34911fcef..2a555f409 100644 --- a/cypress/e2e/item/home/home.cy.ts +++ b/cypress/e2e/item/home/home.cy.ts @@ -1,4 +1,6 @@ import { + GuestFactory, + ItemLoginSchemaFactory, PackedFolderItemFactory, PackedLocalFileItemFactory, } from '@graasp/sdk'; @@ -14,6 +16,7 @@ import { DROPZONE_SELECTOR, HOME_LOAD_MORE_BUTTON_SELECTOR, ITEM_SEARCH_INPUT_ID, + PREVENT_GUEST_MESSAGE_ID, SORTING_ORDERING_SELECTOR_ASC, SORTING_ORDERING_SELECTOR_DESC, SORTING_SELECT_SELECTOR, @@ -211,4 +214,15 @@ describe('Home', () => { }); }); }); + + it('Show message for guest', () => { + const item = PackedFolderItemFactory(); + const guest = GuestFactory({ + itemLoginSchema: ItemLoginSchemaFactory({ item }), + }); + cy.setUpApi({ items: [item], currentMember: guest }); + cy.visit(HOME_PATH); + + cy.get(`#${PREVENT_GUEST_MESSAGE_ID}`).should('be.visible'); + }); }); diff --git a/cypress/e2e/item/publish/viewPublished.cy.ts b/cypress/e2e/item/publish/viewPublished.cy.ts index fd8528fe4..abb5c7250 100644 --- a/cypress/e2e/item/publish/viewPublished.cy.ts +++ b/cypress/e2e/item/publish/viewPublished.cy.ts @@ -1,4 +1,8 @@ -import { PackedFolderItemFactory } from '@graasp/sdk'; +import { + GuestFactory, + ItemLoginSchemaFactory, + PackedFolderItemFactory, +} from '@graasp/sdk'; import { SortingOptions } from '@/components/table/types'; import { BUILDER } from '@/langs/constants'; @@ -8,6 +12,7 @@ import { PUBLISHED_ITEMS_PATH } from '../../../../src/config/paths'; import { CREATE_ITEM_BUTTON_ID, ITEM_SEARCH_INPUT_ID, + PREVENT_GUEST_MESSAGE_ID, PUBLISHED_ITEMS_ERROR_ALERT_ID, PUBLISHED_ITEMS_ID, SORTING_ORDERING_SELECTOR_ASC, @@ -26,6 +31,16 @@ const items = [ const publishedItemData = items.map(({ published }) => published); describe('Published Items', () => { + it('Show message for guest', () => { + const item = PackedFolderItemFactory(); + const guest = GuestFactory({ + itemLoginSchema: ItemLoginSchemaFactory({ item }), + }); + cy.setUpApi({ items: [item], currentMember: guest }); + cy.visit(PUBLISHED_ITEMS_PATH); + cy.get(`#${PREVENT_GUEST_MESSAGE_ID}`).should('be.visible'); + }); + describe('Member has no published items', () => { it('Show empty table', () => { cy.setUpApi({ diff --git a/cypress/e2e/item/trash/viewTrash.cy.ts b/cypress/e2e/item/trash/viewTrash.cy.ts index 3f2884f07..9ae82072f 100644 --- a/cypress/e2e/item/trash/viewTrash.cy.ts +++ b/cypress/e2e/item/trash/viewTrash.cy.ts @@ -1,4 +1,9 @@ -import { PackedRecycledItemDataFactory } from '@graasp/sdk'; +import { + GuestFactory, + ItemLoginSchemaFactory, + PackedFolderItemFactory, + PackedRecycledItemDataFactory, +} from '@graasp/sdk'; import { SortingOptions } from '@/components/table/types'; import { BUILDER } from '@/langs/constants'; @@ -8,6 +13,7 @@ import { RECYCLE_BIN_PATH } from '../../../../src/config/paths'; import { CREATE_ITEM_BUTTON_ID, ITEM_SEARCH_INPUT_ID, + PREVENT_GUEST_MESSAGE_ID, RECYCLED_ITEMS_ERROR_ALERT_ID, RECYCLED_ITEMS_ROOT_CONTAINER, SORTING_ORDERING_SELECTOR_ASC, @@ -24,6 +30,16 @@ const recycledItemData = [ ]; describe('View trash', () => { + it('Show message for guest', () => { + const item = PackedFolderItemFactory(); + const guest = GuestFactory({ + itemLoginSchema: ItemLoginSchemaFactory({ item }), + }); + cy.setUpApi({ items: [item], currentMember: guest }); + cy.visit(RECYCLE_BIN_PATH); + cy.get(`#${PREVENT_GUEST_MESSAGE_ID}`).should('be.visible'); + }); + describe('Member has no recycled items', () => { it('Show empty table', () => { cy.setUpApi({ diff --git a/cypress/e2e/item/view/viewFolder.cy.ts b/cypress/e2e/item/view/viewFolder.cy.ts index 2a261213e..1a45ce200 100644 --- a/cypress/e2e/item/view/viewFolder.cy.ts +++ b/cypress/e2e/item/view/viewFolder.cy.ts @@ -1,10 +1,19 @@ -import { PackedFolderItemFactory } from '@graasp/sdk'; +import { + GuestFactory, + ItemLoginSchemaFactory, + PackedFolderItemFactory, + PermissionLevel, +} from '@graasp/sdk'; import { SortingOptionsForFolder } from '../../../../src/components/table/types'; import i18n from '../../../../src/config/i18n'; import { buildItemPath } from '../../../../src/config/paths'; import { CREATE_ITEM_BUTTON_ID, + ITEM_HEADER_ID, + ITEM_MENU_BOOKMARK_BUTTON_CLASS, + ITEM_MENU_FLAG_BUTTON_CLASS, + ITEM_MENU_SHORTCUT_BUTTON_CLASS, ITEM_SEARCH_INPUT_ID, NAVIGATION_HOME_ID, SORTING_ORDERING_SELECTOR_ASC, @@ -28,8 +37,63 @@ const child4 = PackedFolderItemFactory({ parentItem }); const children = [child1, child2, child3, child4]; const items = [parentItem, item1, ...children]; +describe('View folder as guest', () => { + it('Show limited features', () => { + const item = PackedFolderItemFactory( + {}, + { permission: PermissionLevel.Read }, + ); + const guest = GuestFactory({ + itemLoginSchema: ItemLoginSchemaFactory({ + item, + }), + }); + cy.setUpApi({ + items: [item], + currentMember: guest, + }); + cy.visit(buildItemPath(item.id)); + + // no add button + cy.get(`#${CREATE_ITEM_BUTTON_ID}`).should('not.exist'); + + // menu item only contains flag + cy.get(`#${ITEM_HEADER_ID} [data-testid="MoreVertIcon"]`).click(); + cy.get(`.${ITEM_MENU_FLAG_BUTTON_CLASS}`).should('be.visible'); + cy.get(`.${ITEM_MENU_SHORTCUT_BUTTON_CLASS}`).should('not.exist'); + }); +}); + +describe('View folder as reader', () => { + it('Show limited features', () => { + const item = PackedFolderItemFactory( + {}, + { permission: PermissionLevel.Read }, + ); + cy.setUpApi({ + items: [item], + }); + cy.visit(buildItemPath(item.id)); + + // no add button + cy.get(`#${CREATE_ITEM_BUTTON_ID}`).should('not.exist'); + + // menu item contains flag, duplicate, shortcut, bookmark + cy.get(`#${ITEM_HEADER_ID} [data-testid="MoreVertIcon"]`).click(); + cy.get(`.${ITEM_MENU_FLAG_BUTTON_CLASS}`).should('be.visible'); + cy.get(`.${ITEM_MENU_SHORTCUT_BUTTON_CLASS}`).should('be.visible'); + cy.get(`.${ITEM_MENU_BOOKMARK_BUTTON_CLASS}`).should('be.visible'); + }); +}); + +describe('view Folder as admin', () => { + beforeEach(() => { + cy.setUpApi({ + items, + }); + i18n.changeLanguage(CURRENT_USER.extra.lang as string); + }); -describe('View Folder', () => { it('View folder on map by default', () => { cy.setUpApi({ items, @@ -52,13 +116,6 @@ describe('View Folder', () => { cy.get(`#${CREATE_ITEM_BUTTON_ID}`).should('be.visible'); }); - beforeEach(() => { - cy.setUpApi({ - items, - }); - i18n.changeLanguage(CURRENT_USER.extra.lang as string); - }); - it('visit item by id', () => { const { id } = parentItem; cy.visit(buildItemPath(id, { mode: ItemLayoutMode.Grid })); diff --git a/package.json b/package.json index e70ee3222..a2d12fd56 100644 --- a/package.json +++ b/package.json @@ -20,11 +20,11 @@ "@emotion/styled": "11.13.0", "@graasp/chatbox": "3.3.0", "@graasp/map": "1.18.0", - "@graasp/query-client": "3.22.3", + "@graasp/query-client": "3.22.4", "@graasp/sdk": "4.29.1", "@graasp/stylis-plugin-rtl": "2.2.0", "@graasp/translations": "1.37.0", - "@graasp/ui": "5.0.1", + "@graasp/ui": "5.1.0", "@mui/icons-material": "5.16.4", "@mui/lab": "5.0.0-alpha.172", "@mui/material": "5.16.4", diff --git a/src/components/App.tsx b/src/components/App.tsx index 17ee47c72..5ce466170 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -1,9 +1,17 @@ +import { Trans } from 'react-i18next'; import { Outlet, Route, Routes, useLocation } from 'react-router-dom'; import { buildSignInPath, saveUrlForRedirection } from '@graasp/sdk'; -import { CustomInitialLoader, withAuthorization } from '@graasp/ui'; +import { + CustomInitialLoader, + PreventGuestWrapper, + SignedInWrapper, +} from '@graasp/ui'; import { DOMAIN, GRAASP_AUTH_HOST } from '@/config/env'; +import { useBuilderTranslation } from '@/config/i18n'; +import { PREVENT_GUEST_MESSAGE_ID } from '@/config/selectors'; +import { BUILDER } from '@/langs/constants'; import { BOOKMARKED_ITEMS_PATH, @@ -18,7 +26,7 @@ import { REDIRECT_PATH, buildItemPath, } from '../config/paths'; -import { hooks } from '../config/queryClient'; +import { hooks, mutations } from '../config/queryClient'; import Main from './main/Main'; import Redirect from './main/Redirect'; import BookmarkedItemsScreen from './pages/BookmarkedItemsScreen'; @@ -26,6 +34,7 @@ import MapItemsScreen from './pages/MapItemsScreen'; import PublishedItemsScreen from './pages/PublishedItemsScreen'; import RecycledItemsScreen from './pages/RecycledItemsScreen'; import HomeScreen from './pages/home/HomeScreen'; +import ItemLoginWrapper from './pages/item/ItemLoginWrapper'; import ItemPageLayout from './pages/item/ItemPageLayout'; import ItemScreen from './pages/item/ItemScreen'; import ItemScreenLayout from './pages/item/ItemScreenLayout'; @@ -36,44 +45,18 @@ import LibrarySettingsPage from './pages/item/LibrarySettingsPage'; const { useItemFeedbackUpdates, useCurrentMember } = hooks; const App = (): JSX.Element => { + const { t: translateBuilder } = useBuilderTranslation(); + const { mutate: signOut } = mutations.useSignOut(); const { pathname } = useLocation(); - const { data: currentMember, isLoading } = useCurrentMember(); + const { data: currentAccount, isLoading } = useCurrentMember(); // registers the item updates through websockets - useItemFeedbackUpdates?.(currentMember?.id); + useItemFeedbackUpdates?.(currentAccount?.id); if (isLoading) { return ; } - const withAuthorizationProps = { - currentMember, - redirectionLink: buildSignInPath({ - host: GRAASP_AUTH_HOST, - redirectionUrl: window.location.toString(), - }), - onRedirect: () => { - // save current url for later redirection after sign in - saveUrlForRedirection(pathname, DOMAIN); - }, - }; - const HomeWithAuthorization = withAuthorization( - HomeScreen, - withAuthorizationProps, - ); - const FavoriteWithAuthorization = withAuthorization( - BookmarkedItemsScreen, - withAuthorizationProps, - ); - const PublishedWithAuthorization = withAuthorization( - PublishedItemsScreen, - withAuthorizationProps, - ); - const RecycleWithAuthorization = withAuthorization( - RecycledItemsScreen, - withAuthorizationProps, - ); - return ( } /> @@ -84,16 +67,64 @@ const App = (): JSX.Element => { } > - } /> + {/* pages with personal info */} } - /> + element={ + // redirect to sign in if not signed in + { + // save current url for later redirection after sign in + saveUrlForRedirection(pathname, DOMAIN); + }} + > + signOut()} + errorText={translateBuilder(BUILDER.ERROR_MESSAGE)} + text={ + }} + /> + } + > + + + + } + > + } /> + } + /> + } + /> + } /> + + + {/* item pages - can be public */} } - /> - }> + path={buildItemPath()} + element={ + + + + } + > } /> }> } /> @@ -101,9 +132,9 @@ const App = (): JSX.Element => { } /> - } /> - } /> - } /> + + {/* redirection to home */} + } /> } /> } /> diff --git a/src/components/Root.tsx b/src/components/Root.tsx index c74dabd0c..6d8695271 100644 --- a/src/components/Root.tsx +++ b/src/components/Root.tsx @@ -10,6 +10,7 @@ import { ToastContainer } from 'react-toastify'; import { CssBaseline } from '@mui/material'; +import { AccountType } from '@graasp/sdk'; import { langs } from '@graasp/translations'; import { ThemeProvider } from '@graasp/ui'; @@ -36,7 +37,11 @@ const ThemeWrapper = () => { langs={langs} languageSelectSx={{ mb: 2, mr: 2 }} i18n={i18nConfig} - defaultDirection={i18nConfig.dir(currentMember?.extra?.lang)} + defaultDirection={i18nConfig.dir( + currentMember?.type === AccountType.Individual + ? currentMember?.extra?.lang + : 'ltr', + )} > diff --git a/src/components/common/BookmarkButton.tsx b/src/components/common/BookmarkButton.tsx index 1f7e70981..aa0a80504 100644 --- a/src/components/common/BookmarkButton.tsx +++ b/src/components/common/BookmarkButton.tsx @@ -15,6 +15,7 @@ type Props = { type?: ActionButtonVariant; onClick?: () => void; size?: IconButtonProps['size']; + className?: string; }; const isItemBookmarked = ( @@ -27,17 +28,13 @@ const BookmarkButton = ({ size, type, onClick, + className, }: Props): JSX.Element | null => { - const { data: member } = hooks.useCurrentMember(); const { data: bookmarks } = hooks.useBookmarkedItems(); const { t: translateBuilder } = useBuilderTranslation(); const addFavorite = mutations.useAddBookmarkedItem(); const deleteFavorite = mutations.useRemoveBookmarkedItem(); - if (!member) { - return null; - } - const isFavorite = isItemBookmarked(item, bookmarks); const handleFavorite = () => { @@ -56,6 +53,7 @@ const BookmarkButton = ({ return ( { const { t: translateBuilder } = useBuilderTranslation(); const { mutateAsync: signOut } = mutations.useSignOut(); - const renderAvatar = (m?: CompleteMember | null) => ( - - ); - const redirectPath = buildSignInPath({ host: GRAASP_AUTH_HOST, redirectionUrl: window.location.toString(), @@ -52,7 +48,7 @@ const UserSwitchWrapper = ({ ButtonContent }: Props): JSX.Element => { signOutMenuItemId={HEADER_MEMBER_MENU_SIGN_OUT_BUTTON_ID} seeProfileButtonId={HEADER_MEMBER_MENU_SEE_PROFILE_BUTTON_ID} buildMemberMenuItemId={buildMemberMenuItemId} - renderAvatar={renderAvatar} + avatar={} /> ); }; diff --git a/src/components/context/CurrentUserContext.tsx b/src/components/context/CurrentUserContext.tsx index dc46a742d..5ccbf83f9 100644 --- a/src/components/context/CurrentUserContext.tsx +++ b/src/components/context/CurrentUserContext.tsx @@ -1,5 +1,8 @@ import { ReactNode, createContext, useContext, useEffect } from 'react'; +import { AccountType } from '@graasp/sdk'; +import { DEFAULT_LANG } from '@graasp/translations'; + import i18n from '../../config/i18n'; import { hooks } from '../../config/queryClient'; @@ -19,12 +22,15 @@ export const CurrentUserContextProvider = ({ children }: Props): ReactNode => { const { data } = useCurrentMember(); // update language depending on user setting - const lang = data?.extra?.lang; useEffect(() => { + const lang = + data?.type === AccountType.Individual + ? data?.extra?.lang || DEFAULT_LANG + : DEFAULT_LANG; if (lang !== i18n.language) { i18n.changeLanguage(lang); } - }, [lang]); + }, [data]); return children; }; diff --git a/src/components/item/FolderContent.tsx b/src/components/item/FolderContent.tsx index a50ffc5e6..d0fa2a453 100644 --- a/src/components/item/FolderContent.tsx +++ b/src/components/item/FolderContent.tsx @@ -1,3 +1,5 @@ +import { useOutletContext } from 'react-router-dom'; + import { Alert, Box, Stack, Typography } from '@mui/material'; import { @@ -30,6 +32,7 @@ import { import { useDragSelection } from '../main/list/useDragSelection'; import { DesktopMap } from '../map/DesktopMap'; import NoItemFilters from '../pages/NoItemFilters'; +import { OutletType } from '../pages/item/type'; import SortingSelect from '../table/SortingSelect'; import { SortingOptionsForFolder } from '../table/types'; import { useSorting } from '../table/useSorting'; @@ -43,19 +46,22 @@ type Props = { searchText: string; items?: PackedItem[]; sortBy: SortingOptionsForFolder; + canWrite?: boolean; }; -const Content = ({ item, searchText, items, sortBy }: Props) => { +const Content = ({ + item, + searchText, + items, + sortBy, + canWrite = false, +}: Props) => { const { mode } = useLayoutContext(); const { itemTypes } = useFilterItemsContext(); const { selectedIds, clearSelection, toggleSelection } = useSelectionContext(); const DragSelection = useDragSelection(); - const enableEditing = item.permission - ? PermissionLevelCompare.lte(PermissionLevel.Write, item.permission) - : false; - if (mode === ItemLayoutMode.Map) { return ( @@ -75,7 +81,7 @@ const Content = ({ item, searchText, items, sortBy }: Props) => { onCardClick={toggleSelection} onMove={clearSelection} /> - {Boolean(enableEditing && !searchText && !itemTypes?.length) && ( + {Boolean(canWrite && !searchText && !itemTypes?.length) && ( { const { t: translateBuilder } = useBuilderTranslation(); const { selectedIds } = useSelectionContext(); const itemSearch = useItemSearch(); + const { canWrite } = useOutletContext(); const { data: children, @@ -136,13 +143,13 @@ const FolderContent = ({ item }: { item: PackedItem }): JSX.Element => { ordering: Ordering.ASC, }); - const sortedChildren = children?.sort(sortFn); - const sortingOptions = Object.values(SortingOptionsForFolder).sort((t1, t2) => translateEnums(t1).localeCompare(translateEnums(t2)), ); if (children) { + const sortedChildren = children.sort(sortFn); + return ( <> @@ -160,49 +167,62 @@ const FolderContent = ({ item }: { item: PackedItem }): JSX.Element => { spacing={1} > {itemSearch.input} - + {canWrite && ( + + )} - - {selectedIds.length && sortedChildren?.length ? ( - - ) : ( - - - - {sortBy && setSortBy && ( - - )} - + {sortedChildren.length ? ( + + {selectedIds.length ? ( + + ) : ( + + + + {sortBy && setSortBy && ( + + )} + + - - )} - + )} + + ) : null} + + {/* reader empty message */} + {!sortedChildren.length && !canWrite ? ( + + {translateBuilder(BUILDER.EMPTY_FOLDER_MESSAGE)} + + ) : null} + ( ( diff --git a/src/components/item/header/Actions.tsx b/src/components/item/header/Actions.tsx index a0ce8406a..1a243df68 100644 --- a/src/components/item/header/Actions.tsx +++ b/src/components/item/header/Actions.tsx @@ -4,6 +4,7 @@ import { MoreVert } from '@mui/icons-material'; import { Divider, IconButton, Menu } from '@mui/material'; import { + AccountType, ItemType, PackedItem, PermissionLevel, @@ -13,6 +14,7 @@ import { ActionButton } from '@graasp/ui'; import useModalStatus from '@/components/hooks/useModalStatus'; import { hooks } from '@/config/queryClient'; +import { ITEM_MENU_BOOKMARK_BUTTON_CLASS } from '@/config/selectors'; import BookmarkButton from '../../common/BookmarkButton'; import CollapseButton from '../../common/CollapseButton'; @@ -77,34 +79,47 @@ const Actions = ({ item }: Props): JSX.Element | null => { - { - openCreateShortcutModal(); - closeMenu(); - }} - /> - - {canWrite && ( + {member.type === AccountType.Individual ? ( <> - - - - {item.type !== ItemType.FOLDER && ( - + { + openCreateShortcutModal(); + closeMenu(); + }} + /> + + {canWrite && ( + <> + + + + {item.type !== ItemType.FOLDER && ( + + )} + )} + - )} - + ) : null} {canAdmin && ( { const renderRoot = () => { // no access to root if signed out - if (!currentMember) { + if (currentMember?.type !== AccountType.Individual) { return null; } diff --git a/src/components/main/ItemMenuContent.tsx b/src/components/main/ItemMenuContent.tsx index a35a71ff5..afec4bf63 100644 --- a/src/components/main/ItemMenuContent.tsx +++ b/src/components/main/ItemMenuContent.tsx @@ -4,6 +4,7 @@ import { MoreVert } from '@mui/icons-material'; import { Divider, IconButton, Menu } from '@mui/material'; import { + AccountType, ItemType, PackedItem, PermissionLevel, @@ -97,7 +98,7 @@ const ItemMenuContent = ({ item }: Props): JSX.Element | null => { ) : ( false ), - ...(member && member.id + ...(member.type === AccountType.Individual ? [ { ].filter(Boolean) as JSX.Element[]; const miscMenus = [ - ...(member?.id + ...(member.type === AccountType.Individual ? [ { /** * only override the open prop when user is not logged in * we want to keep the default behavior when the user is logged in + * we close the drawer if the user is a guest */ - currentMember ? undefined : false + currentMember?.type === AccountType.Individual ? undefined : false } context={Context.Builder} headerId={HEADER_APP_BAR_ID} diff --git a/src/components/main/MainMenu.tsx b/src/components/main/MainMenu.tsx index 9a7ee24df..bec7d0aa8 100644 --- a/src/components/main/MainMenu.tsx +++ b/src/components/main/MainMenu.tsx @@ -9,6 +9,7 @@ import { Stack, } from '@mui/material'; +import { AccountType } from '@graasp/sdk'; import { MainMenu as GraaspMainMenu, MenuItem } from '@graasp/ui'; import { @@ -59,6 +60,30 @@ const MainMenu = (): JSX.Element | false => { return false; } + const individualMenuItems = + member.type === AccountType.Individual ? ( + <> + goTo(BOOKMARKED_ITEMS_PATH)} + selected={pathname === BOOKMARKED_ITEMS_PATH} + text={t(BUILDER.BOOKMARKED_ITEMS_TITLE)} + icon={} + /> + goTo(PUBLISHED_ITEMS_PATH)} + selected={pathname === PUBLISHED_ITEMS_PATH} + text={t(BUILDER.NAVIGATION_PUBLISHED_ITEMS_TITLE)} + icon={} + /> + goTo(RECYCLE_BIN_PATH)} + selected={pathname === RECYCLE_BIN_PATH} + text={t(BUILDER.RECYCLE_BIN_TITLE)} + icon={} + /> + + ) : null; + return ( @@ -69,24 +94,7 @@ const MainMenu = (): JSX.Element | false => { icon={} text={t(BUILDER.MY_ITEMS_TITLE)} /> - goTo(BOOKMARKED_ITEMS_PATH)} - selected={pathname === BOOKMARKED_ITEMS_PATH} - text={t(BUILDER.BOOKMARKED_ITEMS_TITLE)} - icon={} - /> - goTo(PUBLISHED_ITEMS_PATH)} - selected={pathname === PUBLISHED_ITEMS_PATH} - text={t(BUILDER.NAVIGATION_PUBLISHED_ITEMS_TITLE)} - icon={} - /> - goTo(RECYCLE_BIN_PATH)} - selected={pathname === RECYCLE_BIN_PATH} - text={t(BUILDER.RECYCLE_BIN_TITLE)} - icon={} - /> + {individualMenuItems} diff --git a/src/components/pages/home/HomeScreen.tsx b/src/components/pages/home/HomeScreen.tsx index cce7a4c8d..64fd7b315 100644 --- a/src/components/pages/home/HomeScreen.tsx +++ b/src/components/pages/home/HomeScreen.tsx @@ -1,6 +1,14 @@ import { useState } from 'react'; -import { Alert, Box, LinearProgress, Stack } from '@mui/material'; +import { + Alert, + Box, + Container, + LinearProgress, + Button as MuiButton, + Stack, + Typography, +} from '@mui/material'; import { Button } from '@graasp/ui'; @@ -197,28 +205,47 @@ const HomeScreenContent = ({ searchText }: { searchText: string }) => { const HomeScreen = (): JSX.Element => { const { t: translateBuilder } = useBuilderTranslation(); + const { data: currentMember } = hooks.useCurrentMember(); const itemSearch = useItemSearch(); + if (currentMember) { + return ( + + {itemSearch.input} + + + } + > + + + + + ); + } + + // not logged in - redirection return ( - - {itemSearch.input} - - - } - > - - - - + + + + + {translateBuilder(BUILDER.REDIRECTION_TEXT)} + + + {translateBuilder(BUILDER.REDIRECTION_BUTTON)} + + + + ); }; diff --git a/src/components/pages/item/ItemLoginWrapper.tsx b/src/components/pages/item/ItemLoginWrapper.tsx index f7a2f7cde..285b4b8ed 100644 --- a/src/components/pages/item/ItemLoginWrapper.tsx +++ b/src/components/pages/item/ItemLoginWrapper.tsx @@ -5,7 +5,6 @@ import { ItemLoginAuthorization } from '@graasp/ui'; import { hooks, mutations } from '@/config/queryClient'; import { ITEM_LOGIN_SIGN_IN_BUTTON_ID, - ITEM_LOGIN_SIGN_IN_MEMBER_ID_ID, ITEM_LOGIN_SIGN_IN_PASSWORD_ID, ITEM_LOGIN_SIGN_IN_USERNAME_ID, } from '@/config/selectors'; @@ -14,29 +13,43 @@ import ItemForbiddenScreen from '../../main/list/ItemForbiddenScreen'; const { useItem, useCurrentMember, useItemLoginSchemaType } = hooks; -const ItemLoginWrapper = (WrappedComponent: () => JSX.Element): JSX.Element => { +const ItemLoginWrapper = ({ + children, +}: { + children: JSX.Element; +}): JSX.Element => { const { mutate: itemLoginSignIn } = mutations.usePostItemLogin(); + const { data: currentAccount, isLoading: isCurrentAccountLoading } = + useCurrentMember(); const { itemId } = useParams(); + const { data: item, isLoading: isItemLoading } = useItem(itemId); + const { data: itemLoginSchemaType, isLoading: isItemLoginSchemaTypeLoading } = + useItemLoginSchemaType({ itemId }); - const ForbiddenContent = ; + const forbiddenContent = ; if (!itemId) { - return ForbiddenContent; + return forbiddenContent; } - const AuthComponent = ItemLoginAuthorization({ - signIn: itemLoginSignIn, - itemId, - useCurrentMember, - useItem, - useItemLoginSchemaType, - ForbiddenContent, - memberIdInputId: ITEM_LOGIN_SIGN_IN_MEMBER_ID_ID, - usernameInputId: ITEM_LOGIN_SIGN_IN_USERNAME_ID, - signInButtonId: ITEM_LOGIN_SIGN_IN_BUTTON_ID, - passwordInputId: ITEM_LOGIN_SIGN_IN_PASSWORD_ID, - })(WrappedComponent); - return ; + return ( + + {children} + + ); }; export default ItemLoginWrapper; diff --git a/src/components/pages/item/ItemPageLayout.tsx b/src/components/pages/item/ItemPageLayout.tsx index cd237759b..0ec3f8504 100644 --- a/src/components/pages/item/ItemPageLayout.tsx +++ b/src/components/pages/item/ItemPageLayout.tsx @@ -14,7 +14,6 @@ import { buildItemPath } from '@/config/paths'; import ErrorAlert from '../../common/ErrorAlert'; import { useLayoutContext } from '../../context/LayoutContext'; -import WrappedAuthItemScreen from './ItemLoginWrapper'; import { OutletType } from './type'; const ItemPageLayout = (): JSX.Element => { @@ -50,7 +49,4 @@ const ItemPageLayout = (): JSX.Element => { return ; }; -const WrappedItemScreen = (): JSX.Element => - WrappedAuthItemScreen(ItemPageLayout); - -export default WrappedItemScreen; +export default ItemPageLayout; diff --git a/src/components/pages/item/ItemScreenLayout.tsx b/src/components/pages/item/ItemScreenLayout.tsx index cb2bf25b5..e1fe58297 100644 --- a/src/components/pages/item/ItemScreenLayout.tsx +++ b/src/components/pages/item/ItemScreenLayout.tsx @@ -7,7 +7,6 @@ import { Loader } from '@graasp/ui'; import { hooks } from '../../../config/queryClient'; import ErrorAlert from '../../common/ErrorAlert'; import { useLayoutContext } from '../../context/LayoutContext'; -import WrappedAuthItemScreen from './ItemLoginWrapper'; const { useItem } = hooks; @@ -44,7 +43,4 @@ const ItemScreenLayout = (): JSX.Element => { return ; }; -const WrappedItemScreen = (): JSX.Element => - WrappedAuthItemScreen(ItemScreenLayout); - -export default WrappedItemScreen; +export default ItemScreenLayout; diff --git a/src/components/table/ItemActions.tsx b/src/components/table/ItemActions.tsx index 644211bc3..8fde34324 100644 --- a/src/components/table/ItemActions.tsx +++ b/src/components/table/ItemActions.tsx @@ -1,6 +1,8 @@ import { Stack } from '@mui/material'; -import { DiscriminatedItem } from '@graasp/sdk'; +import { AccountType, DiscriminatedItem } from '@graasp/sdk'; + +import { hooks } from '@/config/queryClient'; import BookmarkButton from '../common/BookmarkButton'; import DownloadButton from '../main/DownloadButton'; @@ -10,11 +12,17 @@ type Props = { }; // items and memberships match by index -const ItemActions = ({ data: item }: Props): JSX.Element => ( - - - - -); +const ItemActions = ({ data: item }: Props): JSX.Element => { + const { data: currentMember } = hooks.useCurrentMember(); + + return ( + + {currentMember?.type === AccountType.Individual && ( + + )} + + + ); +}; export default ItemActions; diff --git a/src/config/selectors.ts b/src/config/selectors.ts index f37416687..d9eaf3cf8 100644 --- a/src/config/selectors.ts +++ b/src/config/selectors.ts @@ -71,7 +71,6 @@ export const ITEM_LOGIN_SIGN_IN_PASSWORD_ID = 'itemLoginSignInPassword'; export const ITEM_LOGIN_SIGN_IN_BUTTON_ID = 'itemLoginSignInButton'; export const ITEM_SCREEN_MAIN_ID = 'itemScreenMain'; export const ITEM_LOGIN_SCREEN_FORBIDDEN_ID = 'itemLoginScreenForbidden'; -export const ITEM_LOGIN_SIGN_IN_MEMBER_ID_ID = 'itemLoginSignInMemberId'; export const ITEM_LOGIN_SIGN_IN_MODE_ID = 'itemLoginSignInMode'; export const ITEM_MAIN_CLASS = 'itemMain'; export const HOME_ERROR_ALERT_ID = 'homeErrorAlert'; @@ -81,6 +80,7 @@ export const PUBLISHED_ITEMS_ERROR_ALERT_ID = 'publishedItemsErrorAlert'; export const RECYCLED_ITEMS_ERROR_ALERT_ID = 'recycledItemsErrorAlert'; export const ITEM_MENU_SHORTCUT_BUTTON_CLASS = 'itemMenuShortcutButton'; export const ITEM_MENU_DUPLICATE_BUTTON_CLASS = 'itemMenuDuplicateButton'; +export const ITEM_MENU_BOOKMARK_BUTTON_CLASS = 'itemMenuBookmarkButton'; export const ITEM_MENU_FAVORITE_BUTTON_CLASS = 'itemMenuFavoriteButton'; export const ITEM_MENU_FLAG_BUTTON_CLASS = 'itemMenuFlagButton'; export const buildFlagListItemId = (type: string): string => @@ -434,3 +434,4 @@ export const RECYCLE_BIN_RESTORE_MANY_ITEMS_BUTTON_ID = export const COPY_MANY_ITEMS_BUTTON_SELECTOR = `.lucide-copy`; export const MOVE_MANY_ITEMS_BUTTON_SELECTOR = `.lucide-move`; export const DELETE_SINGLE_ITEM_BUTTON_SELECTOR = `.lucide-trash`; +export const PREVENT_GUEST_MESSAGE_ID = 'preventGuestMessage'; diff --git a/src/langs/ar.json b/src/langs/ar.json index 660802369..ef41c7402 100644 --- a/src/langs/ar.json +++ b/src/langs/ar.json @@ -70,7 +70,6 @@ "EDIT_ITEM_ERROR_MESSAGE": "العنصر غير صالح", "EDIT_ITEM_MODAL_TITLE": "تعديل العنصر", "EDIT_ITEM_IMAGE_ALT_TEXT_LABEL": "نص بديل (لأغراض إمكانية الوصول - accessibility)", - "EMPTY_ITEM_MESSAGE": "هذا العنصر فارغ.", "ERROR_MESSAGE": "حدث خطأ.", "BOOKMARKED_ITEM_ADD_TEXT": "اضافة الى قائمة المفضّلات", "BOOKMARKED_ITEM_REMOVE_TEXT": "إزالة من قائمة المفضّلات", diff --git a/src/langs/constants.ts b/src/langs/constants.ts index d28b95b74..fdd1aa198 100644 --- a/src/langs/constants.ts +++ b/src/langs/constants.ts @@ -68,7 +68,7 @@ export const BUILDER = { EDIT_ITEM_ERROR_MESSAGE: 'EDIT_ITEM_ERROR_MESSAGE', EDIT_ITEM_MODAL_TITLE: 'EDIT_ITEM_MODAL_TITLE', EDIT_ITEM_IMAGE_ALT_TEXT_LABEL: 'EDIT_ITEM_IMAGE_ALT_TEXT_LABEL', - EMPTY_ITEM_MESSAGE: 'EMPTY_ITEM_MESSAGE', + EMPTY_FOLDER_MESSAGE: 'EMPTY_FOLDER_MESSAGE', ERROR_MESSAGE: 'ERROR_MESSAGE', BOOKMARKED_ITEM_ADD_TEXT: 'BOOKMARKED_ITEM_ADD_TEXT', BOOKMARKED_ITEM_REMOVE_TEXT: 'BOOKMARKED_ITEM_REMOVE_TEXT', @@ -570,4 +570,8 @@ export const BUILDER = { MEMBER_VALIDATION_TITLE: 'MEMBER_VALIDATION_TITLE', MEMBER_VALIDATION_DESCRIPTION: 'MEMBER_VALIDATION_DESCRIPTION', MEMBER_VALIDATION_LINK_TEXT: 'MEMBER_VALIDATION_LINK_TEXT', + GUEST_LIMITATION_TEXT: 'GUEST_LIMITATION_TEXT', + GUEST_SIGN_OUT_BUTTON: 'GUEST_SIGN_OUT_BUTTON', + REDIRECTION_TEXT: 'REDIRECTION_TEXT', + REDIRECTION_BUTTON: 'REDIRECTION_BUTTON', }; diff --git a/src/langs/de.json b/src/langs/de.json index 5ebc239f1..26eb55add 100644 --- a/src/langs/de.json +++ b/src/langs/de.json @@ -72,7 +72,6 @@ "EDIT_ITEM_ERROR_MESSAGE": "Element ist ungültig", "EDIT_ITEM_MODAL_TITLE": "Objekt bearbeiten", "EDIT_ITEM_IMAGE_ALT_TEXT_LABEL": "Alternativer Text (aus Gründen der Barrierefreiheit)", - "EMPTY_ITEM_MESSAGE": "Dieser Artikel ist leer.", "ERROR_MESSAGE": "Es ist ein Fehler aufgetreten.", "BOOKMARKED_ITEM_ADD_TEXT": "Zu den Favoriten hinzufügen", "BOOKMARKED_ITEM_REMOVE_TEXT": "Von Favoriten entfernen", diff --git a/src/langs/en.json b/src/langs/en.json index c34330627..37a19c0b8 100644 --- a/src/langs/en.json +++ b/src/langs/en.json @@ -72,7 +72,7 @@ "EDIT_ITEM_ERROR_MESSAGE": "Item is invalid", "EDIT_ITEM_MODAL_TITLE": "Edit Item", "EDIT_ITEM_IMAGE_ALT_TEXT_LABEL": "Alternative text (for accessibility purposes)", - "EMPTY_ITEM_MESSAGE": "This item is empty.", + "EMPTY_FOLDER_MESSAGE": "This folder is empty.", "ERROR_MESSAGE": "An error occured.", "BOOKMARKED_ITEM_ADD_TEXT": "Add to Bookmarks", "BOOKMARKED_ITEM_REMOVE_TEXT": "Remove from Bookmarks", @@ -473,5 +473,7 @@ "item.order": "Order", "MEMBER_VALIDATION_TITLE": "Account email needs to be verified", "MEMBER_VALIDATION_DESCRIPTION": "In order to use all features of Graasp you need to validate your account email. You will find more information in the link below.", - "MEMBER_VALIDATION_LINK_TEXT": "Learn more" + "MEMBER_VALIDATION_LINK_TEXT": "Learn more", + "GUEST_LIMITATION_TEXT": "You are currently using Graasp with the guest account <1>{{name}}. In order to use all features of Graasp, you have to log out and create a Graasp account.", + "GUEST_SIGN_OUT_BUTTON": " Log out and Create a Graasp account" } diff --git a/src/langs/es.json b/src/langs/es.json index 7369f94a3..5b79a16f9 100644 --- a/src/langs/es.json +++ b/src/langs/es.json @@ -71,7 +71,6 @@ "EDIT_ITEM_ERROR_MESSAGE": "El artículo no es válido", "EDIT_ITEM_MODAL_TITLE": "Editar artículo", "EDIT_ITEM_IMAGE_ALT_TEXT_LABEL": "Texto alternativo (para fines de accesibilidad)", - "EMPTY_ITEM_MESSAGE": "Este artículo está vacío.", "ERROR_MESSAGE": "Ocurrió un error.", "BOOKMARKED_ITEM_ADD_TEXT": "Agregar a los favoritos", "BOOKMARKED_ITEM_REMOVE_TEXT": "Quitar de favoritos", diff --git a/src/langs/fr.json b/src/langs/fr.json index 64a24a188..f77130976 100644 --- a/src/langs/fr.json +++ b/src/langs/fr.json @@ -71,7 +71,7 @@ "EDIT_ITEM_ERROR_MESSAGE": "L'élément n'est pas valide", "EDIT_ITEM_MODAL_TITLE": "Modifier l'élément", "EDIT_ITEM_IMAGE_ALT_TEXT_LABEL": "Texte alternatif (pour améliorer l'accessibilité)", - "EMPTY_ITEM_MESSAGE": "Cet élément est vide.", + "EMPTY_FOLDER_MESSAGE": "Ce dossier est vide.", "ERROR_MESSAGE": "Une erreur est survenue.", "BOOKMARKED_ITEM_ADD_TEXT": "Ajouter aux signets", "BOOKMARKED_ITEM_REMOVE_TEXT": "Retirer des signets", diff --git a/src/langs/it.json b/src/langs/it.json index ba603cf6e..1d6fb4e93 100644 --- a/src/langs/it.json +++ b/src/langs/it.json @@ -71,7 +71,6 @@ "EDIT_ITEM_ERROR_MESSAGE": "L'elemento non è valido", "EDIT_ITEM_MODAL_TITLE": "Modifica elemento", "EDIT_ITEM_IMAGE_ALT_TEXT_LABEL": "Testo alternativo (per accessibilità)", - "EMPTY_ITEM_MESSAGE": "Questo elemento è vuoto.", "ERROR_MESSAGE": "Si è verificato un errore.", "BOOKMARKED_ITEM_ADD_TEXT": "Aggiungi ai preferiti", "BOOKMARKED_ITEM_REMOVE_TEXT": "Rimuovi dai preferiti", diff --git a/yarn.lock b/yarn.lock index 5dc32201b..215e7734d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1672,19 +1672,19 @@ __metadata: languageName: node linkType: hard -"@graasp/query-client@npm:3.22.3": - version: 3.22.3 - resolution: "@graasp/query-client@npm:3.22.3" +"@graasp/query-client@npm:3.22.4": + version: 3.22.4 + resolution: "@graasp/query-client@npm:3.22.4" dependencies: "@tanstack/react-query": "npm:4.36.1" "@tanstack/react-query-devtools": "npm:4.36.1" - axios: "npm:1.7.5" + axios: "npm:1.7.7" http-status-codes: "npm:2.3.0" peerDependencies: "@graasp/sdk": ^4.0.0 "@graasp/translations": "*" react: ^18.0.0 - checksum: 10/a1cf0e884e77ca928f7b983fdde7e3b7ca55b23c93d6ec8d79cd3e4db7f675a2e153a5072a0085d8aa5fce977ef7e06de684f23d4e37737ca28ddfccf0914eb7 + checksum: 10/e4c5958141a55aa41f5a68fc39b8cc3ac2bf123a47f8b7a4c8c05836274f962082227353ed1ad93c10ec1b25a10691eb29bcd72c31f38f7243bae2cdfd7fd250 languageName: node linkType: hard @@ -1723,9 +1723,9 @@ __metadata: languageName: node linkType: hard -"@graasp/ui@npm:5.0.1": - version: 5.0.1 - resolution: "@graasp/ui@npm:5.0.1" +"@graasp/ui@npm:5.1.0": + version: 5.1.0 + resolution: "@graasp/ui@npm:5.1.0" dependencies: http-status-codes: "npm:2.3.0" interweave: "npm:13.1.0" @@ -1754,7 +1754,7 @@ __metadata: react-i18next: ^13.0.0 || ^14.0.0 || ^15.0.0 react-router-dom: ^6.11.0 stylis: ^4.1.3 - checksum: 10/2acc246b741493ea17178f37d1fb8cc6d3120a6fc90b46bbe5b911e9c3cd37d51d80546c981652ff7a59cccd26ea10f7499e543d0ba8722de5ee456e6c162186 + checksum: 10/fcab9f3d021848e81f8779be9508a6e6efcae249a364611e4adf888410658d83dd1ecfe8b0fbcda26c3bd3f840b44e6efe73cd2e3f9af53917c4510499a3b37a languageName: node linkType: hard @@ -3745,17 +3745,6 @@ __metadata: languageName: node linkType: hard -"axios@npm:1.7.5": - version: 1.7.5 - resolution: "axios@npm:1.7.5" - dependencies: - follow-redirects: "npm:^1.15.6" - form-data: "npm:^4.0.0" - proxy-from-env: "npm:^1.1.0" - checksum: 10/6cbcfe943a84089f420a900a3a3aeb54ee94dcc9c2b81b150434896357be5d1079eff0b1bbb628597371e79f896b1bc5776df04184756ba99656ff31df9a75bf - languageName: node - linkType: hard - "axios@npm:1.7.7": version: 1.7.7 resolution: "axios@npm:1.7.7" @@ -6508,11 +6497,11 @@ __metadata: "@emotion/styled": "npm:11.13.0" "@graasp/chatbox": "npm:3.3.0" "@graasp/map": "npm:1.18.0" - "@graasp/query-client": "npm:3.22.3" + "@graasp/query-client": "npm:3.22.4" "@graasp/sdk": "npm:4.29.1" "@graasp/stylis-plugin-rtl": "npm:2.2.0" "@graasp/translations": "npm:1.37.0" - "@graasp/ui": "npm:5.0.1" + "@graasp/ui": "npm:5.1.0" "@mui/icons-material": "npm:5.16.4" "@mui/lab": "npm:5.0.0-alpha.172" "@mui/material": "npm:5.16.4"