Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: update main component and refactor #491

Merged
merged 7 commits into from
Feb 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions cypress/e2e/navigation.cy.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { PermissionLevel } from '@graasp/sdk';

import { buildMainPath } from '@/config/paths';
import { SHOW_MORE_ITEMS_ID, buildTreeItemClass } from '@/config/selectors';
import {
HOME_PAGE_PAGINATION_ID,
buildHomePaginationId,
buildTreeItemClass,
} from '@/config/selectors';

import {
FOLDER_WITH_SUBFOLDER_ITEM,
Expand All @@ -27,8 +31,8 @@ describe('Navigation', () => {
cy.visit('/');

cy.wait(['@getCurrentMember', '@getAccessibleItems']);

cy.get(`#${SHOW_MORE_ITEMS_ID}`).click();
cy.get(`#${HOME_PAGE_PAGINATION_ID}`).scrollIntoView().should('be.visible');
cy.get(`#${buildHomePaginationId(2)}`).click();
});

it('Expand folder when navigating', () => {
Expand All @@ -38,8 +42,11 @@ describe('Navigation', () => {

const child = FOLDER_WITH_SUBFOLDER_ITEM.items[1];
const childOfChild = FOLDER_WITH_SUBFOLDER_ITEM.items[3];
cy.get(`.${buildTreeItemClass(child.id)}`).click();
cy.get(`.${buildTreeItemClass(childOfChild.id)}`).should('be.visible');
// we need to to use the `:visible` meta selector because there are 2 navigations (one for mobile hidden, and one for desktop)
cy.get(`.${buildTreeItemClass(child.id)}:visible`).click();
cy.get(`.${buildTreeItemClass(childOfChild.id)}:visible`).should(
'be.visible',
);
});

it('show all folders for partial order', () => {
Expand Down
11 changes: 8 additions & 3 deletions cypress/support/integrationUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,14 @@ export const expectLinkViewScreenLayout = ({

if (!html) {
if (settings?.showLinkButton ?? DEFAULT_LINK_SHOW_BUTTON) {
cy.get('[data-testid="OpenInNewIcon"]').should('be.visible');
cy.get('[data-testid="OpenInNewIcon"]')
.scrollIntoView()
.should('be.visible');
} else {
// button should not be shown when the setting is false
cy.get('[data-testid="OpenInNewIcon"]').should('not.exist');
cy.get('[data-testid="OpenInNewIcon"]')
.scrollIntoView()
.should('not.exist');
}
}
};
Expand Down Expand Up @@ -138,7 +142,8 @@ export const expectFolderLayout = ({
.filter(({ type }) => type === ItemType.FOLDER)
.forEach(({ id }) => {
// click in mainmenu
cy.get(`.${buildTreeItemClass(id)}`).click();
// there are two because of th two menus
cy.get(`.${buildTreeItemClass(id)}:visible`).click();

expectFolderLayout({ rootId: id, items });
});
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@
"@emotion/react": "11.11.1",
"@emotion/styled": "11.11.0",
"@graasp/chatbox": "3.0.3",
"@graasp/query-client": "2.2.3",
"@graasp/query-client": "2.4.2",
"@graasp/sdk": "3.5.0",
"@graasp/translations": "1.21.1",
"@graasp/ui": "4.4.0",
"@graasp/ui": "4.5.0",
"@mui/icons-material": "5.14.19",
"@mui/lab": "5.0.0-alpha.151",
"@mui/material": "5.14.19",
Expand Down
10 changes: 7 additions & 3 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import { useCurrentMemberContext } from '@/contexts/CurrentMemberContext';
import HomePage from '@/modules/pages/HomePage';
import ItemPage from '@/modules/pages/ItemPage';

import PageWrapper from './modules/layout/PageWrapper';

export const App = (): JSX.Element => {
const location = useLocation();
const [searchParams, setSearchParams] = useSearchParams();
Expand Down Expand Up @@ -50,9 +52,11 @@ export const App = (): JSX.Element => {

return (
<Routes>
<Route path={buildMainPath()} element={<ItemPage />} />
<Route path={HOME_PATH} element={<HomePageWithAuthorization />} />
<Route element={<Navigate to={HOME_PATH} />} />
<Route element={<PageWrapper />}>
<Route path={buildMainPath()} element={<ItemPage />} />
<Route path={HOME_PATH} element={<HomePageWithAuthorization />} />
<Route path="*" element={<Navigate to={HOME_PATH} />} />
spaenleh marked this conversation as resolved.
Show resolved Hide resolved
</Route>
</Routes>
);
};
Expand Down
8 changes: 7 additions & 1 deletion src/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import { ToastContainer } from 'react-toastify';
import { CssBaseline, GlobalStyles } from '@mui/material';
import { ThemeProvider } from '@mui/material/styles';

// todo: set locale based on member local using
// https://mui.com/material-ui/customization/theming/#api
// and https://mui.com/material-ui/guides/localization/#locale-text
// with the deepMerge util function
import { theme } from '@graasp/ui';

import { SHOW_NOTIFICATIONS } from '@/config/env';
Expand Down Expand Up @@ -36,7 +40,9 @@ const Root = (): JSX.Element => (
</Router>
</ThemeProvider>
</I18nextProvider>
{import.meta.env.DEV && <ReactQueryDevtools />}
{import.meta.env.DEV && import.meta.env.MODE !== 'test' && (
<ReactQueryDevtools />
)}
</QueryClientProvider>
);

Expand Down
3 changes: 0 additions & 3 deletions src/config/queryClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@ const {
enableWebsocket: true,
defaultQueryOptions: {
keepPreviousData: true,
// avoid refetching when same data are closely fetched
staleTime: 1000, // ms
cacheTime: 1000, // ms
},
});
export {
Expand Down
4 changes: 4 additions & 0 deletions src/config/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ export const OWN_ITEMS_GRID_ID = 'ownItemsGrid';
export const buildMemberAvatarClass = (id?: string): string =>
`memberAvatar-${id}`;

export const HOME_PAGE_PAGINATION_ID = 'homePagePagination';
export const buildHomePaginationId = (page: number | null): string =>
`homePagination-${page}`;

export const APP_NAVIGATION_PLATFORM_SWITCH_ID = 'appNavigationPlatformSwitch';
export const APP_NAVIGATION_PLATFORM_SWITCH_BUTTON_IDS = {
[Platform.Builder]: 'appNavigationPlatformSwitchButtonBuilder',
Expand Down
10 changes: 6 additions & 4 deletions src/contexts/ItemContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import { DiscriminatedItem } from '@graasp/sdk';
import { hooks } from '@/config/queryClient';

type Props = {
rootId: string;
rootId?: string;
children: JSX.Element | JSX.Element[];
};

type ItemContextType = {
rootId: string;
rootId?: string;
focusedItemId?: string;
setFocusedItemId: (id: string) => void;
rootItem?: DiscriminatedItem;
Expand All @@ -24,7 +24,9 @@ type ItemContextType = {
const ItemContext = React.createContext<ItemContextType>({
rootId: '',
focusedItemId: '',
setFocusedItemId: () => null,
setFocusedItemId: (id: string) => {
console.error(`Called setFocusedItemId(${id}) without a matching Provider`);
},
isRootItemLoading: true,
isRootItemError: false,
isDescendantsLoading: true,
Expand All @@ -42,7 +44,7 @@ const ItemContextProvider = ({ children, rootId }: Props): JSX.Element => {
data: descendants,
isLoading: isDescendantsLoading,
isError: isDescendantsError,
} = hooks.useDescendants({ id: rootId, enabled: true });
} = hooks.useDescendants({ id: rootId ?? '', enabled: true });

const value = useMemo(
() => ({
Expand Down
4 changes: 4 additions & 0 deletions src/langs/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,8 @@ export const PLAYER = {
HIDDEN_WRAPPER_TOOLTIP: 'HIDDEN_WRAPPER_TOOLTIP',
SHOW_MORE: 'SHOW_MORE',
RECENT_ITEMS_TITLE: 'RECENT_ITEMS_TITLE',
DRAWER_ARIAL_LABEL: 'DRAWER_ARIAL_LABEL',
ERROR_FETCHING_ITEM: 'ERROR_FETCHING_ITEM',
ERROR_ACCESSING_ITEM: 'ERROR_ACCESSING_ITEM',
ERROR_ACCESSING_ITEM_HELPER: 'ERROR_ACCESSING_ITEM_HELPER',
};
6 changes: 5 additions & 1 deletion src/langs/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,9 @@
"LOAD_MORE": "Load more",
"HIDDEN_WRAPPER_TOOLTIP": "This element is hidden, you can see it because you have admin or write access, users with read access won't see it",
"SHOW_MORE": "Show More...",
"RECENT_ITEMS_TITLE": "Most Recent Items"
"RECENT_ITEMS_TITLE": "Most Recent Items",
"DRAWER_ARIAL_LABEL": "Open drawer",
"ERROR_FETCHING_ITEM": "There was an error fetching the requested item",
"ERROR_ACCESSING_ITEM": "You cannot access this item",
"ERROR_ACCESSING_ITEM_HELPER": "Your current account does not have the rights to access this item."
}
57 changes: 20 additions & 37 deletions src/modules/item/ItemForbiddenScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@ import AccountCircleIcon from '@mui/icons-material/AccountCircle';
import BlockIcon from '@mui/icons-material/Block';
import { IconButton, Stack, Typography } from '@mui/material';

import { Context } from '@graasp/sdk';
import { AUTH } from '@graasp/translations';
import { Button, Main } from '@graasp/ui';
import { Button } from '@graasp/ui';

import { useAuthTranslation } from '@/config/i18n';
import HeaderNavigation from '@/modules/header/HeaderNavigation';
import HeaderRightContent from '@/modules/header/HeaderRightContent';
import { PLAYER } from '@/langs/constants';
import UserSwitchWrapper from '@/modules/userSwitch/UserSwitchWrapper';

const ItemForbiddenScreen: FC = () => {
Expand All @@ -25,40 +23,25 @@ const ItemForbiddenScreen: FC = () => {
);

return (
<Main
open={false}
context={Context.Player}
headerLeftContent={<HeaderNavigation topItemName="" />}
headerRightContent={<HeaderRightContent />}
<Stack
direction="column"
justifyContent="center"
alignItems="center"
textAlign="center"
margin="auto"
height="100%"
>
<Stack
direction="column"
justifyContent="center"
alignItems="center"
textAlign="center"
margin="auto"
height="100%"
>
<Typography variant="h4">
<IconButton size="large">
<BlockIcon />
</IconButton>
{
// todo: add translations
t('You cannot access this item')
}
</Typography>
<Typography variant="body1">
{
// todo: add translations
t(
'Your current account does not have the rights to access this item.',
)
}
</Typography>
<UserSwitchWrapper ButtonContent={ButtonContent} preserveUrl />
</Stack>
</Main>
<Typography variant="h4">
<IconButton size="large">
<BlockIcon />
</IconButton>
{t(PLAYER.ERROR_ACCESSING_ITEM)}
</Typography>
<Typography variant="body1">
{t(PLAYER.ERROR_ACCESSING_ITEM_HELPER)}
</Typography>
<UserSwitchWrapper ButtonContent={ButtonContent} preserveUrl />
</Stack>
);
};

Expand Down
61 changes: 20 additions & 41 deletions src/modules/item/MainScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,71 +1,50 @@
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom';

import { Alert, Skeleton, Typography } from '@mui/material';

import { Context } from '@graasp/sdk';
import { Main, useMobileView } from '@graasp/ui';

import { ROOT_ID_PATH } from '@/config/paths';
import { hooks } from '@/config/queryClient';
import { useItemContext } from '@/contexts/ItemContext';
import { LayoutContextProvider } from '@/contexts/LayoutContext';
import HeaderNavigation from '@/modules/header/HeaderNavigation';
import HeaderRightContent from '@/modules/header/HeaderRightContent';
import { PLAYER } from '@/langs/constants';
import SideContent from '@/modules/rightPanel/SideContent';

import Item from './Item';
import ItemNavigation from './ItemNavigation';

const MainScreen = (): JSX.Element => {
const { isMobile } = useMobileView();

const MainScreen = (): JSX.Element | null => {
const rootId = useParams()[ROOT_ID_PATH];
const { focusedItemId } = useItemContext();
const mainId = focusedItemId || rootId;
const { data: item, isLoading, isError } = hooks.useItem(mainId);
const { t } = useTranslation();
const [topItemName, setTopItemName] = useState('');
const [isFirstItem, setIsFirstItem] = useState(true);

if (isLoading) {
return <Skeleton variant="rectangular" width="100%" />;
}

if (!item || isError) {
// todo: add this to translations
return <Alert severity="error">{t('This item does not exist')}</Alert>;
}

if (isFirstItem) {
setTopItemName(item.name);
setIsFirstItem(false);
}

const content = !rootId ? (
const content = rootId ? (
<Item id={mainId} />
) : (
<Typography align="center" variant="h4">
{t('No item defined.')}
</Typography>
) : (
<Item id={mainId} />
);

return (
<Main
open={!isMobile && Boolean(rootId)}
context={Context.Player}
sidebar={<ItemNavigation />}
headerLeftContent={
<HeaderNavigation rootId={rootId} topItemName={topItemName} />
}
headerRightContent={<HeaderRightContent />}
>
if (item) {
return (
<LayoutContextProvider>
<SideContent item={item} content={content} />
</LayoutContextProvider>
</Main>
);
);
}

if (isLoading) {
return <Skeleton variant="rectangular" width="100%" />;
}

if (isError) {
// todo: add this to translations
return <Alert severity="error">{t(PLAYER.ERROR_FETCHING_ITEM)}</Alert>;
}

return null;
};

export default MainScreen;
Loading
Loading