🚚(app-desk) integrate next router with menu
Integrate next router with the menu. Create most of the pages.
This commit is contained in:
49
src/frontend/apps/desk/src/app/InnerLayout.tsx
Normal file
49
src/frontend/apps/desk/src/app/InnerLayout.tsx
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { Loader } from '@openfun/cunningham-react';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
import useAuthStore from '@/auth/useAuthStore';
|
||||||
|
import { Box } from '@/components';
|
||||||
|
|
||||||
|
import { HEADER_HEIGHT, Header } from './header';
|
||||||
|
import { Menu } from './menu';
|
||||||
|
|
||||||
|
export default function InnerLayout({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
const { initAuth, authenticated, initialized } = useAuthStore();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
initAuth();
|
||||||
|
}, [initAuth, initialized]);
|
||||||
|
|
||||||
|
if (!authenticated) {
|
||||||
|
return (
|
||||||
|
<Box $height="100vh" $width="100vw" $align="center" $justify="center">
|
||||||
|
<Loader />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box as="main" $direction="column" $height="100vh" $css="overflow:hidden;">
|
||||||
|
<Header />
|
||||||
|
<Box $css="flex: 1;">
|
||||||
|
<Menu />
|
||||||
|
<Box
|
||||||
|
$direction="column"
|
||||||
|
$height={`calc(100vh - ${HEADER_HEIGHT})`}
|
||||||
|
$width="100%"
|
||||||
|
$css="overflow: auto;"
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ describe('Page', () => {
|
|||||||
expect(screen.getByRole('status')).toBeInTheDocument();
|
expect(screen.getByRole('status')).toBeInTheDocument();
|
||||||
|
|
||||||
expect(screen.getByRole('heading', { level: 1 })).toHaveTextContent(
|
expect(screen.getByRole('heading', { level: 1 })).toHaveTextContent(
|
||||||
'Hello Desk!',
|
'Hello Desk !',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
7
src/frontend/apps/desk/src/app/contacts/page.tsx
Normal file
7
src/frontend/apps/desk/src/app/contacts/page.tsx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { Box } from '@/components';
|
||||||
|
|
||||||
|
export default function Contacts() {
|
||||||
|
return <Box>Contacts</Box>;
|
||||||
|
}
|
||||||
7
src/frontend/apps/desk/src/app/favorite/page.tsx
Normal file
7
src/frontend/apps/desk/src/app/favorite/page.tsx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { Box } from '@/components';
|
||||||
|
|
||||||
|
export default function Favorite() {
|
||||||
|
return <Box>Favorite</Box>;
|
||||||
|
}
|
||||||
7
src/frontend/apps/desk/src/app/groups/page.tsx
Normal file
7
src/frontend/apps/desk/src/app/groups/page.tsx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { Box } from '@/components';
|
||||||
|
|
||||||
|
export default function Groups() {
|
||||||
|
return <Box>Groups</Box>;
|
||||||
|
}
|
||||||
7
src/frontend/apps/desk/src/app/help/page.tsx
Normal file
7
src/frontend/apps/desk/src/app/help/page.tsx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { Box } from '@/components';
|
||||||
|
|
||||||
|
export default function Help() {
|
||||||
|
return <Box>Help</Box>;
|
||||||
|
}
|
||||||
@@ -5,7 +5,9 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|||||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
||||||
|
|
||||||
import { useCunninghamTheme } from '@/cunningham';
|
import { useCunninghamTheme } from '@/cunningham';
|
||||||
|
|
||||||
import '@/i18n/initI18n';
|
import '@/i18n/initI18n';
|
||||||
|
import InnerLayout from './InnerLayout';
|
||||||
|
|
||||||
import './globals.css';
|
import './globals.css';
|
||||||
|
|
||||||
@@ -23,7 +25,9 @@ export default function RootLayout({
|
|||||||
<body suppressHydrationWarning={process.env.NODE_ENV === 'development'}>
|
<body suppressHydrationWarning={process.env.NODE_ENV === 'development'}>
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<ReactQueryDevtools />
|
<ReactQueryDevtools />
|
||||||
<CunninghamProvider theme={theme}>{children}</CunninghamProvider>
|
<CunninghamProvider theme={theme}>
|
||||||
|
<InnerLayout>{children}</InnerLayout>
|
||||||
|
</CunninghamProvider>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -25,12 +25,12 @@ export const Menu = () => {
|
|||||||
$justify="space-between"
|
$justify="space-between"
|
||||||
$direction="column"
|
$direction="column"
|
||||||
>
|
>
|
||||||
<Box $direction="column">
|
<Box className="pt-b" $direction="column" $gap="0.8rem">
|
||||||
<MenuItem Icon={IconSearch} label={t('Search')} />
|
<MenuItem Icon={IconSearch} label={t('Search')} href="/" />
|
||||||
<MenuItem Icon={IconFavorite} label={t('Favorite')} />
|
<MenuItem Icon={IconFavorite} label={t('Favorite')} href="/favorite" />
|
||||||
<MenuItem Icon={IconRecent} label={t('Recent')} />
|
<MenuItem Icon={IconRecent} label={t('Recent')} href="/recent" />
|
||||||
<MenuItem Icon={IconContacts} label={t('Contacts')} />
|
<MenuItem Icon={IconContacts} label={t('Contacts')} href="/contacts" />
|
||||||
<MenuItem Icon={IconGroup} label={t('Groups')} />
|
<MenuItem Icon={IconGroup} label={t('Groups')} href="/groups" />
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { Button } from '@openfun/cunningham-react';
|
import { Button } from '@openfun/cunningham-react';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { usePathname } from 'next/navigation';
|
||||||
import React, { useRef, useState } from 'react';
|
import React, { useRef, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import { Box } from '@/components/';
|
import { Box } from '@/components/';
|
||||||
import { useCunninghamTheme } from '@/cunningham';
|
import { useCunninghamTheme } from '@/cunningham';
|
||||||
@@ -8,41 +11,71 @@ import { SVGComponent } from '@/types/components';
|
|||||||
|
|
||||||
import { Tooltip } from './Tooltip';
|
import { Tooltip } from './Tooltip';
|
||||||
|
|
||||||
|
const StyledLink = styled(Link)`
|
||||||
|
text-decoration: none;
|
||||||
|
color: #ffffff33;
|
||||||
|
&[aria-current='page'] {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
interface MenuItemProps {
|
interface MenuItemProps {
|
||||||
Icon: SVGComponent;
|
Icon: SVGComponent;
|
||||||
label: string;
|
label: string;
|
||||||
|
href: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MenuItem = ({ Icon, label }: MenuItemProps) => {
|
const MenuItem = ({ Icon, label, href }: MenuItemProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const buttonRef = useRef(null);
|
const pathname = usePathname();
|
||||||
const { colorsTokens } = useCunninghamTheme();
|
const { colorsTokens } = useCunninghamTheme();
|
||||||
|
const buttonRef = useRef(null);
|
||||||
const [isTooltipOpen, setIsTooltipOpen] = useState(false);
|
const [isTooltipOpen, setIsTooltipOpen] = useState(false);
|
||||||
|
|
||||||
|
const isActive = pathname === href;
|
||||||
|
const { color, background, colorTooltip, backgroundTooltip } = isActive
|
||||||
|
? {
|
||||||
|
color: colorsTokens()['primary-600'],
|
||||||
|
background: colorsTokens()['primary-300'],
|
||||||
|
backgroundTooltip: 'white',
|
||||||
|
colorTooltip: 'black',
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
color: '#ffffff55',
|
||||||
|
background: undefined,
|
||||||
|
backgroundTooltip: '#161616',
|
||||||
|
colorTooltip: 'white',
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<StyledLink
|
||||||
|
href={href}
|
||||||
|
aria-current={isActive && 'page'}
|
||||||
|
ref={buttonRef}
|
||||||
|
onMouseOver={() => setIsTooltipOpen(true)}
|
||||||
|
onMouseOut={() => setIsTooltipOpen(false)}
|
||||||
|
>
|
||||||
<Box
|
<Box
|
||||||
className="m-st p-t"
|
className="m-st p-t"
|
||||||
as="li"
|
as="li"
|
||||||
$justify="center"
|
$justify="center"
|
||||||
ref={buttonRef}
|
|
||||||
$css={`
|
$css={`
|
||||||
& > button { padding: 0};
|
& > button { padding: 0};
|
||||||
transition: all 0.2s ease-in-out
|
transition: all 0.2s ease-in-out
|
||||||
`}
|
`}
|
||||||
$background={colorsTokens()['primary-300']}
|
$background={background}
|
||||||
$radius="10px"
|
$radius="10px"
|
||||||
onMouseOver={() => setIsTooltipOpen(true)}
|
|
||||||
onMouseOut={() => setIsTooltipOpen(false)}
|
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
aria-label={t(`{{label}} button`, { label })}
|
aria-label={t(`{{label}} button`, { label })}
|
||||||
icon={
|
icon={
|
||||||
<Box $color={colorsTokens()['primary-600']}>
|
<Box $color={color}>
|
||||||
<Icon
|
<Icon
|
||||||
width="2.375rem"
|
width="2.375rem"
|
||||||
aria-label={t(`{{label}} icon`, { label })}
|
aria-label={t(`{{label}} icon`, { label })}
|
||||||
color="#ffffff"
|
style={{
|
||||||
|
transition: 'color 0.2s ease-in-out',
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
@@ -55,11 +88,11 @@ const MenuItem = ({ Icon, label }: MenuItemProps) => {
|
|||||||
<Tooltip
|
<Tooltip
|
||||||
buttonRef={buttonRef}
|
buttonRef={buttonRef}
|
||||||
label={label}
|
label={label}
|
||||||
backgroundColor="#ffffff"
|
backgroundColor={backgroundTooltip}
|
||||||
textColor="#000000"
|
textColor={colorTooltip}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</StyledLink>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,47 +1,18 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { Loader } from '@openfun/cunningham-react';
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import useAuthStore from '@/auth/useAuthStore';
|
|
||||||
import { Box } from '@/components';
|
import { Box } from '@/components';
|
||||||
|
|
||||||
import Header, { HEADER_HEIGHT } from './Header/Header';
|
|
||||||
import { Teams } from './Teams';
|
import { Teams } from './Teams';
|
||||||
import { MENU_WIDTH, Menu } from './menu';
|
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const { initAuth, authenticated, initialized } = useAuthStore();
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (initialized) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
initAuth();
|
|
||||||
}, [initAuth, initialized]);
|
|
||||||
|
|
||||||
if (!authenticated) {
|
|
||||||
return <Loader />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main>
|
<Box $direction="column" className="p-b">
|
||||||
<Header />
|
<h1>{t('Hello Desk !')}</h1>
|
||||||
<Box $css={`margin-top:${HEADER_HEIGHT}`}>
|
<Teams />
|
||||||
<Menu />
|
</Box>
|
||||||
<Box
|
|
||||||
$css={`margin-left:${MENU_WIDTH}`}
|
|
||||||
$direction="column"
|
|
||||||
$height="300vh"
|
|
||||||
className="p-b"
|
|
||||||
>
|
|
||||||
<h1>{t('Hello Desk !')}</h1>
|
|
||||||
<Teams />
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</main>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
7
src/frontend/apps/desk/src/app/recent/page.tsx
Normal file
7
src/frontend/apps/desk/src/app/recent/page.tsx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { Box } from '@/components';
|
||||||
|
|
||||||
|
export default function Recent() {
|
||||||
|
return <Box>Recent</Box>;
|
||||||
|
}
|
||||||
@@ -1,26 +1,72 @@
|
|||||||
import { expect, test } from "@playwright/test";
|
import { expect, test } from '@playwright/test';
|
||||||
|
|
||||||
import { keyCloakSignIn } from "./common";
|
import { keyCloakSignIn } from './common';
|
||||||
|
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
await page.goto("/");
|
await page.goto('/');
|
||||||
await keyCloakSignIn(page);
|
await keyCloakSignIn(page);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.describe("Menu", () => {
|
test.describe('Menu', () => {
|
||||||
test("checks all the elements are visible", async ({ page }) => {
|
const menuItems = [
|
||||||
const menu = page.locator("menu").first();
|
{ name: 'Search', isDefault: true },
|
||||||
|
{ name: 'Favorite', isDefault: false },
|
||||||
|
{ name: 'Recent', isDefault: false },
|
||||||
|
{ name: 'Contacts', isDefault: false },
|
||||||
|
{ name: 'Groups', isDefault: false },
|
||||||
|
];
|
||||||
|
for (const { name, isDefault } of menuItems) {
|
||||||
|
test(`checks that ${name} menu item is displaying correctly`, async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const menu = page.locator('menu').first();
|
||||||
|
|
||||||
await expect(menu.getByLabel("Search button")).toBeVisible();
|
const buttonMenu = menu.getByLabel(`${name} button`);
|
||||||
await expect(menu.getByLabel("Favoris button")).toBeVisible();
|
await expect(buttonMenu).toBeVisible();
|
||||||
await expect(menu.getByLabel("Recent button")).toBeVisible();
|
await buttonMenu.hover();
|
||||||
await expect(menu.getByLabel("Contacts button")).toBeVisible();
|
await expect(menu.getByLabel('tooltip')).toHaveText(name);
|
||||||
await expect(menu.getByLabel("Groups button")).toBeVisible();
|
|
||||||
|
|
||||||
await menu.getByLabel("Search button").hover();
|
// Checks the tooltip is with inactive color
|
||||||
await expect(menu.getByLabel("tooltip")).toHaveText("Search");
|
await expect(menu.getByLabel('tooltip')).toHaveCSS(
|
||||||
|
'background-color',
|
||||||
|
isDefault ? 'rgb(255, 255, 255)' : 'rgb(22, 22, 22)',
|
||||||
|
);
|
||||||
|
|
||||||
await menu.getByLabel("Contacts button").hover();
|
await buttonMenu.click();
|
||||||
await expect(menu.getByLabel("tooltip")).toHaveText("Contacts");
|
|
||||||
});
|
// Checks the tooltip has active color
|
||||||
|
await buttonMenu.hover();
|
||||||
|
await expect(menu.getByLabel('tooltip')).toHaveCSS(
|
||||||
|
'background-color',
|
||||||
|
'rgb(255, 255, 255)',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(`checks that ${name} menu item is routing correctly`, async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await expect(
|
||||||
|
page.locator('h1').first().getByText('Hello Desk !'),
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
const menu = page.locator('menu').first();
|
||||||
|
|
||||||
|
const buttonMenu = menu.getByLabel(`${name} button`);
|
||||||
|
await buttonMenu.click();
|
||||||
|
|
||||||
|
// eslint-disable-next-line playwright/no-conditional-in-test
|
||||||
|
if (isDefault) {
|
||||||
|
await expect(
|
||||||
|
page.locator('h1').first().getByText('Hello Desk !'),
|
||||||
|
).toBeVisible();
|
||||||
|
} else {
|
||||||
|
await expect(
|
||||||
|
page.locator('h1').first().getByText('Hello Desk !'),
|
||||||
|
).toBeHidden();
|
||||||
|
|
||||||
|
const reg = new RegExp(name.toLowerCase());
|
||||||
|
await expect(page).toHaveURL(reg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user