Refactor Layout (#322)

* Refactor Layout

* Fix storybook

* Fixing tests by forcing msw version before regression
This commit is contained in:
Charles Bochet
2023-06-17 21:24:15 +02:00
committed by GitHub
parent 5ae5f28dcb
commit 49462c69a2
38 changed files with 325 additions and 451 deletions

View File

@ -127,7 +127,7 @@
"eslint-plugin-twenty": "file:../packages/eslint-plugin-twenty", "eslint-plugin-twenty": "file:../packages/eslint-plugin-twenty",
"http-server": "^14.1.1", "http-server": "^14.1.1",
"mock-apollo-client": "^1.2.1", "mock-apollo-client": "^1.2.1",
"msw": "^1.2.1", "msw": "1.2.1",
"msw-storybook-addon": "^1.8.0", "msw-storybook-addon": "^1.8.0",
"prettier": "^2.8.0", "prettier": "^2.8.0",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",

View File

@ -2,7 +2,7 @@
/* tslint:disable */ /* tslint:disable */
/** /**
* Mock Service Worker (1.2.2). * Mock Service Worker (1.2.1).
* @see https://github.com/mswjs/msw * @see https://github.com/mswjs/msw
* - Please do NOT modify this file. * - Please do NOT modify this file.
* - Please do NOT serve this file on production. * - Please do NOT serve this file on production.

View File

@ -1,5 +1,7 @@
import { Navigate, Route, Routes } from 'react-router-dom'; import { Navigate, Route, Routes } from 'react-router-dom';
import { DefaultLayout } from '@/ui/layout/DefaultLayout';
import { RequireAuth } from './modules/auth/components/RequireAuth'; import { RequireAuth } from './modules/auth/components/RequireAuth';
import { AuthCallback } from './pages/auth/AuthCallback'; import { AuthCallback } from './pages/auth/AuthCallback';
import { Login } from './pages/auth/Login'; import { Login } from './pages/auth/Login';
@ -10,37 +12,39 @@ import { SettingsProfile } from './pages/settings/SettingsProfile';
export function App() { export function App() {
return ( return (
<Routes> <DefaultLayout>
<Route <Routes>
path="*" <Route
element={ path="*"
<RequireAuth> element={
<RequireAuth>
<Routes>
<Route path="" element={<Navigate to="/people" replace />} />
<Route path="people" element={<People />} />
<Route path="companies" element={<Companies />} />
<Route path="opportunities" element={<Opportunities />} />
</Routes>
</RequireAuth>
}
/>
<Route
path="auth/*"
element={
<Routes> <Routes>
<Route path="" element={<Navigate to="/people" replace />} /> <Route path="callback" element={<AuthCallback />} />
<Route path="people" element={<People />} /> <Route path="login" element={<Login />} />
<Route path="companies" element={<Companies />} />
<Route path="opportunities" element={<Opportunities />} />
</Routes> </Routes>
</RequireAuth> }
} />
/> <Route
<Route path="settings/*"
path="auth/*" element={
element={ <Routes>
<Routes> <Route path="profile" element={<SettingsProfile />} />
<Route path="callback" element={<AuthCallback />} /> </Routes>
<Route path="login" element={<Login />} /> }
</Routes> />
} </Routes>
/> </DefaultLayout>
<Route
path="settings/*"
element={
<Routes>
<Route path="profile" element={<SettingsProfile />} />
</Routes>
}
/>
</Routes>
); );
} }

View File

@ -1,6 +1,7 @@
import { useMatch, useResolvedPath } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import { useTheme } from '@emotion/react'; import { useTheme } from '@emotion/react';
import { SettingsNavbar } from '@/settings/components/SettingsNavbar';
import { import {
IconBuildingSkyscraper, IconBuildingSkyscraper,
IconInbox, IconInbox,
@ -16,51 +17,51 @@ import NavWorkspaceButton from './modules/ui/layout/navbar/NavWorkspaceButton';
export function AppNavbar() { export function AppNavbar() {
const theme = useTheme(); const theme = useTheme();
const currentPath = useLocation().pathname;
const shouldDiplaySubNavBar = currentPath.match(/\/settings\//g) !== null;
return ( return (
<> <>
<NavWorkspaceButton /> {!shouldDiplaySubNavBar ? (
<NavItemsContainer> <>
<NavItem <NavWorkspaceButton />
label="Search" <NavItemsContainer>
to="/search" <NavItem
icon={<IconSearch size={theme.iconSizeMedium} />} label="Search"
soon={true} to="/search"
/> icon={<IconSearch size={theme.iconSizeMedium} />}
<NavItem soon={true}
label="Inbox" />
to="/inbox" <NavItem
icon={<IconInbox size={theme.iconSizeMedium} />} label="Inbox"
soon={true} to="/inbox"
/> icon={<IconInbox size={theme.iconSizeMedium} />}
<NavItem soon={true}
label="Settings" />
to="/settings/profile" <NavItem
icon={<IconSettings size={theme.iconSizeMedium} />} label="Settings"
/> to="/settings/profile"
<NavTitle label="Workspace" /> icon={<IconSettings size={theme.iconSizeMedium} />}
<NavItem />
label="People" <NavTitle label="Workspace" />
to="/people" <NavItem
icon={<IconUser size={theme.iconSizeMedium} />} label="People"
active={ to="/people"
!!useMatch({ icon={<IconUser size={theme.iconSizeMedium} />}
path: useResolvedPath('/people').pathname, active={currentPath === '/people'}
end: true, />
}) <NavItem
} label="Companies"
/> to="/companies"
<NavItem icon={<IconBuildingSkyscraper size={theme.iconSizeMedium} />}
label="Companies" active={currentPath === '/companies'}
to="/companies" />
icon={<IconBuildingSkyscraper size={theme.iconSizeMedium} />} </NavItemsContainer>
active={ </>
!!useMatch({ ) : (
path: useResolvedPath('/companies').pathname, <SettingsNavbar />
end: true, )}
})
}
/>
</NavItemsContainer>
</> </>
); );
} }

View File

@ -1,11 +0,0 @@
import { DefaultLayout } from '@/ui/layout/DefaultLayout';
import { AppNavbar } from './AppNavbar';
type OwnProps = {
children: JSX.Element;
};
export function AppPage({ children }: OwnProps) {
return <DefaultLayout Navbar={AppNavbar}>{children}</DefaultLayout>;
}

View File

@ -1,6 +1,7 @@
import { MemoryRouter } from 'react-router-dom';
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import { getRenderWrapperForPage } from '~/testing/renderWrappers'; import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
import { CommandMenu } from '../CommandMenu'; import { CommandMenu } from '../CommandMenu';
@ -13,5 +14,9 @@ export default meta;
type Story = StoryObj<typeof CommandMenu>; type Story = StoryObj<typeof CommandMenu>;
export const Default: Story = { export const Default: Story = {
render: getRenderWrapperForPage(<CommandMenu initiallyOpen={true} />), render: getRenderWrapperForComponent(
<MemoryRouter>
<CommandMenu initiallyOpen={true} />
</MemoryRouter>,
),
}; };

View File

@ -8,16 +8,15 @@ import {
IconSettings, IconSettings,
IconUser, IconUser,
} from '@/ui/icons/index'; } from '@/ui/icons/index';
import NavBackButton from '@/ui/layout/navbar//NavBackButton';
import NavItem from '@/ui/layout/navbar/NavItem'; import NavItem from '@/ui/layout/navbar/NavItem';
import NavItemsContainer from '@/ui/layout/navbar/NavItemsContainer'; import NavItemsContainer from '@/ui/layout/navbar/NavItemsContainer';
import NavTitle from '@/ui/layout/navbar/NavTitle'; import NavTitle from '@/ui/layout/navbar/NavTitle';
import SubNavbarContainer from '@/ui/layout/navbar/sub-navbar/SubNavBarContainer';
export function SettingsNavbar() { export function SettingsNavbar() {
const theme = useTheme(); const theme = useTheme();
return ( return (
<> <SubNavbarContainer backButtonTitle="Settings">
<NavBackButton title="Settings" />
<NavItemsContainer> <NavItemsContainer>
<NavTitle label="User" /> <NavTitle label="User" />
<NavItem <NavItem
@ -64,6 +63,6 @@ export function SettingsNavbar() {
danger={true} danger={true}
/> />
</NavItemsContainer> </NavItemsContainer>
</> </SubNavbarContainer>
); );
} }

View File

@ -1,11 +0,0 @@
import { SecondaryLayout } from '@/ui/layout/SecondaryLayout';
import { SettingsNavbar } from './SettingsNavbar';
type OwnProps = {
children: JSX.Element;
};
export function SettingsPage({ children }: OwnProps) {
return <SecondaryLayout Navbar={SettingsNavbar}>{children}</SecondaryLayout>;
}

View File

@ -3,6 +3,7 @@ import { useRecoilState, useRecoilValue } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState'; import { currentUserState } from '@/auth/states/currentUserState';
import { CommandMenu } from '@/search/components/CommandMenu'; import { CommandMenu } from '@/search/components/CommandMenu';
import { AppNavbar } from '~/AppNavbar';
import { NavbarContainer } from './navbar/NavbarContainer'; import { NavbarContainer } from './navbar/NavbarContainer';
import { isNavbarOpenedState } from './states/isNavbarOpenedState'; import { isNavbarOpenedState } from './states/isNavbarOpenedState';
@ -25,9 +26,8 @@ const MainContainer = styled.div`
overflow: hidden; overflow: hidden;
width: ${() => width: ${() =>
useRecoilValue(isNavbarOpenedState) useRecoilValue(isNavbarOpenedState)
? `(calc(100% - ${NAVBAR_WIDTH})` ? `calc(100% - ${NAVBAR_WIDTH})`
: '100%'}; : '100%'};
@media (max-width: ${MOBILE_VIEWPORT}px) { @media (max-width: ${MOBILE_VIEWPORT}px) {
width: ${() => (useRecoilValue(isNavbarOpenedState) ? '0' : '100%')}; width: ${() => (useRecoilValue(isNavbarOpenedState) ? '0' : '100%')};
} }
@ -35,10 +35,9 @@ const MainContainer = styled.div`
type OwnProps = { type OwnProps = {
children: JSX.Element; children: JSX.Element;
Navbar: () => JSX.Element;
}; };
export function DefaultLayout({ children, Navbar }: OwnProps) { export function DefaultLayout({ children }: OwnProps) {
const currentUser = useRecoilState(currentUserState); const currentUser = useRecoilState(currentUserState);
const userIsAuthenticated = !!currentUser; const userIsAuthenticated = !!currentUser;
@ -48,7 +47,7 @@ export function DefaultLayout({ children, Navbar }: OwnProps) {
<> <>
<CommandMenu /> <CommandMenu />
<NavbarContainer> <NavbarContainer>
<Navbar /> <AppNavbar />
</NavbarContainer> </NavbarContainer>
<MainContainer>{children}</MainContainer> <MainContainer>{children}</MainContainer>
</> </>

View File

@ -6,6 +6,7 @@ const StyledPanel = styled.div`
border: 1px solid ${(props) => props.theme.primaryBorder}; border: 1px solid ${(props) => props.theme.primaryBorder};
border-radius: 8px; border-radius: 8px;
display: flex; display: flex;
flex-direction: row;
width: 100%; width: 100%;
`; `;

View File

@ -1,68 +0,0 @@
import styled from '@emotion/styled';
import { NavbarContainer } from './navbar/NavbarContainer';
import { MOBILE_VIEWPORT } from './styles/themes';
type OwnProps = {
children: JSX.Element;
Navbar: () => JSX.Element;
};
const StyledLayout = styled.div`
background: ${(props) => props.theme.noisyBackground};
display: flex;
flex-direction: row;
height: 100vh;
position: relative;
width: 100vw;
`;
const MainContainer = styled.div`
display: flex;
flex-direction: row;
overflow: hidden;
width: 100%;
`;
const SubContainer = styled.div`
background: ${(props) => props.theme.primaryBackground};
border: 1px solid ${(props) => props.theme.primaryBorder};
border-radius: ${(props) => props.theme.spacing(2)};
display: flex;
flex-direction: column;
margin: ${(props) => props.theme.spacing(4)};
max-width: calc(100vw - 500px);
padding: ${(props) => props.theme.spacing(8)};
width: 100%;
@media (max-width: ${MOBILE_VIEWPORT}px) {
width: 100%;
max-width: none;
}
`;
const SubSubContainer = styled.div`
color: ${(props) => props.theme.text100};
display: flex;
flex-direction: column;
gap: 32px;
width: 350px;
@media (max-width: ${MOBILE_VIEWPORT}px) {
width: 100%;
}
`;
export const SecondaryLayout = ({ children, Navbar }: OwnProps) => {
return (
<StyledLayout>
<NavbarContainer layout="secondary">
<Navbar />
</NavbarContainer>
<MainContainer>
<SubContainer>
<SubSubContainer>{children}</SubSubContainer>
</SubContainer>
</MainContainer>
</StyledLayout>
);
};

View File

@ -0,0 +1,53 @@
import styled from '@emotion/styled';
import { useRecoilState } from 'recoil';
import { Panel } from '../Panel';
import { RightDrawer } from '../right-drawer/components/RightDrawer';
import { isRightDrawerOpenState } from '../right-drawer/states/isRightDrawerOpenState';
type OwnProps = {
children: JSX.Element;
topMargin?: number;
};
const MainContainer = styled.div<{ topMargin: number }>`
background: ${(props) => props.theme.noisyBackground};
display: flex;
flex-direction: row;
gap: ${(props) => props.theme.spacing(2)};
height: calc(100% - ${(props) => props.topMargin}px);
padding-bottom: ${(props) => props.theme.spacing(3)};
padding-right: ${(props) => props.theme.spacing(3)};
width: calc(100% - ${(props) => props.theme.spacing(3)});
`;
type LeftContainerProps = {
isRightDrawerOpen?: boolean;
};
const LeftContainer = styled.div<LeftContainerProps>`
display: flex;
position: relative;
width: calc(
100% -
${(props) =>
props.isRightDrawerOpen
? `${props.theme.rightDrawerWidth} - ${props.theme.spacing(2)}`
: '0px'}
);
`;
export function ContentContainer({ children, topMargin }: OwnProps) {
const [isRightDrawerOpen] = useRecoilState(isRightDrawerOpenState);
return (
<MainContainer topMargin={topMargin ?? 0}>
<LeftContainer isRightDrawerOpen={isRightDrawerOpen}>
<Panel>{children}</Panel>
</LeftContainer>
<RightDrawer />
</MainContainer>
);
}

View File

@ -0,0 +1,21 @@
import styled from '@emotion/styled';
import { ContentContainer } from './ContentContainer';
type OwnProps = {
children: JSX.Element;
};
const StyledContainer = styled.div`
display: flex;
padding-top: ${(props) => props.theme.spacing(4)};
width: 100%;
`;
export function NoTopBarContainer({ children }: OwnProps) {
return (
<StyledContainer>
<ContentContainer topMargin={16}>{children}</ContentContainer>
</StyledContainer>
);
}

View File

@ -1,12 +1,10 @@
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useRecoilState } from 'recoil';
import { Panel } from '../Panel';
import { RightDrawer } from '../right-drawer/components/RightDrawer';
import { isRightDrawerOpenState } from '../right-drawer/states/isRightDrawerOpenState';
import { TOP_BAR_MIN_HEIGHT, TopBar } from '../top-bar/TopBar'; import { TOP_BAR_MIN_HEIGHT, TopBar } from '../top-bar/TopBar';
import { ContentContainer } from './ContentContainer';
type OwnProps = { type OwnProps = {
children: JSX.Element; children: JSX.Element;
title: string; title: string;
@ -20,53 +18,18 @@ const StyledContainer = styled.div`
width: 100%; width: 100%;
`; `;
const MainContainer = styled.div`
background: ${(props) => props.theme.noisyBackground};
display: flex;
flex-direction: row;
gap: ${(props) => props.theme.spacing(2)};
height: calc(
100% - ${TOP_BAR_MIN_HEIGHT} - ${(props) => props.theme.spacing(2)} -
${(props) => props.theme.spacing(5)}
);
padding-bottom: ${(props) => props.theme.spacing(3)};
padding-right: ${(props) => props.theme.spacing(3)};
width: calc(100% - ${(props) => props.theme.spacing(3)});
`;
type LeftContainerProps = {
isRightDrawerOpen?: boolean;
};
const LeftContainer = styled.div<LeftContainerProps>`
display: flex;
position: relative;
width: calc(
100% -
${(props) =>
props.isRightDrawerOpen
? `${props.theme.rightDrawerWidth} - ${props.theme.spacing(2)}`
: '0px'}
);
`;
export function WithTopBarContainer({ export function WithTopBarContainer({
children, children,
title, title,
icon, icon,
onAddButtonClick, onAddButtonClick,
}: OwnProps) { }: OwnProps) {
const [isRightDrawerOpen] = useRecoilState(isRightDrawerOpenState);
return ( return (
<StyledContainer> <StyledContainer>
<TopBar title={title} icon={icon} onAddButtonClick={onAddButtonClick} /> <TopBar title={title} icon={icon} onAddButtonClick={onAddButtonClick} />
<MainContainer> <ContentContainer topMargin={TOP_BAR_MIN_HEIGHT}>
<LeftContainer isRightDrawerOpen={isRightDrawerOpen}> {children}
<Panel>{children}</Panel> </ContentContainer>
</LeftContainer>
<RightDrawer />
</MainContainer>
</StyledContainer> </StyledContainer>
); );
} }

View File

@ -8,6 +8,7 @@ const StyledNavItemsContainer = styled.div`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin-top: 40px; margin-top: 40px;
min-width: 220px;
`; `;
function NavItemsContainer({ children }: OwnProps) { function NavItemsContainer({ children }: OwnProps) {

View File

@ -4,10 +4,9 @@ import { useRecoilValue } from 'recoil';
import { isNavbarOpenedState } from '../states/isNavbarOpenedState'; import { isNavbarOpenedState } from '../states/isNavbarOpenedState';
import { MOBILE_VIEWPORT } from '../styles/themes'; import { MOBILE_VIEWPORT } from '../styles/themes';
const StyledNavbarContainer = styled.div<{ width: string }>` const StyledNavbarContainer = styled.div`
flex-direction: column; flex-direction: column;
width: ${(props) => width: ${(props) => (useRecoilValue(isNavbarOpenedState) ? 'auto' : '0')};
useRecoilValue(isNavbarOpenedState) ? props.width : '0'};
padding: ${(props) => props.theme.spacing(2)}; padding: ${(props) => props.theme.spacing(2)};
flex-shrink: 0; flex-shrink: 0;
overflow: hidden; overflow: hidden;
@ -19,18 +18,6 @@ const StyledNavbarContainer = styled.div<{ width: string }>`
: '0'}; : '0'};
`; `;
const NavbarSubContainer = styled.div`
display: flex;
flex-direction: column;
margin-left: auto;
margin-top: 41px;
width: 160px;
@media (max-width: ${MOBILE_VIEWPORT}px) {
width: 100%;
}
`;
const NavbarContent = styled.div` const NavbarContent = styled.div`
display: ${() => (useRecoilValue(isNavbarOpenedState) ? 'block' : 'none')}; display: ${() => (useRecoilValue(isNavbarOpenedState) ? 'block' : 'none')};
`; `;
@ -44,18 +31,8 @@ export const NavbarContainer: React.FC<NavbarProps> = ({
children, children,
layout, layout,
}) => { }) => {
if (layout === 'secondary') {
return (
<StyledNavbarContainer width="500px">
<NavbarSubContainer>
<NavbarContent>{children}</NavbarContent>
</NavbarSubContainer>
</StyledNavbarContainer>
);
}
return ( return (
<StyledNavbarContainer width="220px"> <StyledNavbarContainer>
<NavbarContent>{children}</NavbarContent> <NavbarContent>{children}</NavbarContent>
</StyledNavbarContainer> </StyledNavbarContainer>
); );

View File

@ -3,7 +3,7 @@ import styled from '@emotion/styled';
import { IconChevronLeft } from '@/ui/icons/index'; import { IconChevronLeft } from '@/ui/icons/index';
import NavCollapseButton from './NavCollapseButton'; import NavCollapseButton from '../NavCollapseButton';
type OwnProps = { type OwnProps = {
title: string; title: string;

View File

@ -0,0 +1,32 @@
import styled from '@emotion/styled';
import NavBackButton from './NavBackButton';
type OwnProps = {
children: JSX.Element;
backButtonTitle: string;
};
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
padding-left: 300px;
padding-top: ${(props) => props.theme.spacing(6)};
`;
const StyledNavItemsContainer = styled.div`
display: flex;
flex-direction: column;
`;
export default function SubNavbarContainer({
children,
backButtonTitle,
}: OwnProps) {
return (
<StyledContainer>
<NavBackButton title={backButtonTitle} />
<StyledNavItemsContainer>{children}</StyledNavItemsContainer>
</StyledContainer>
);
}

View File

@ -5,7 +5,7 @@ import { IconPlus } from '@/ui/icons/index';
import NavCollapseButton from '../navbar/NavCollapseButton'; import NavCollapseButton from '../navbar/NavCollapseButton';
export const TOP_BAR_MIN_HEIGHT = '40px'; export const TOP_BAR_MIN_HEIGHT = 40;
const TopBarContainer = styled.div` const TopBarContainer = styled.div`
align-items: center; align-items: center;
@ -14,7 +14,7 @@ const TopBarContainer = styled.div`
display: flex; display: flex;
flex-direction: row; flex-direction: row;
font-size: 14px; font-size: 14px;
min-height: ${TOP_BAR_MIN_HEIGHT}; min-height: ${TOP_BAR_MIN_HEIGHT}px;
padding: ${(props) => props.theme.spacing(2)}; padding: ${(props) => props.theme.spacing(2)};
`; `;

View File

@ -24,7 +24,6 @@ import { IconBuildingSkyscraper } from '@/ui/icons/index';
import { IconList } from '@/ui/icons/index'; import { IconList } from '@/ui/icons/index';
import { WithTopBarContainer } from '@/ui/layout/containers/WithTopBarContainer'; import { WithTopBarContainer } from '@/ui/layout/containers/WithTopBarContainer';
import { BoolExpType } from '@/utils/interfaces/generic.interface'; import { BoolExpType } from '@/utils/interfaces/generic.interface';
import { AppPage } from '~/AppPage';
import { CompanyOrderByWithRelationInput as Companies_Order_By } from '~/generated/graphql'; import { CompanyOrderByWithRelationInput as Companies_Order_By } from '~/generated/graphql';
import { TableActionBarButtonCreateCommentThreadCompany } from './table/TableActionBarButtonCreateCommentThreadCompany'; import { TableActionBarButtonCreateCommentThreadCompany } from './table/TableActionBarButtonCreateCommentThreadCompany';
@ -76,31 +75,29 @@ export function Companies() {
const companiesColumns = useCompaniesColumns(); const companiesColumns = useCompaniesColumns();
const theme = useTheme(); const theme = useTheme();
return ( return (
<AppPage> <WithTopBarContainer
<WithTopBarContainer title="Companies"
title="Companies" icon={<IconBuildingSkyscraper size={theme.iconSizeMedium} />}
icon={<IconBuildingSkyscraper size={theme.iconSizeMedium} />} onAddButtonClick={handleAddButtonClick}
onAddButtonClick={handleAddButtonClick} >
> <>
<> <StyledCompaniesContainer>
<StyledCompaniesContainer> <EntityTable
<EntityTable data={companies}
data={companies} columns={companiesColumns}
columns={companiesColumns} viewName="All Companies"
viewName="All Companies" viewIcon={<IconList size={16} />}
viewIcon={<IconList size={16} />} availableSorts={availableSorts}
availableSorts={availableSorts} availableFilters={availableFilters}
availableFilters={availableFilters} onSortsUpdate={updateSorts}
onSortsUpdate={updateSorts} onFiltersUpdate={updateFilters}
onFiltersUpdate={updateFilters} />
/> </StyledCompaniesContainer>
</StyledCompaniesContainer> <EntityTableActionBar>
<EntityTableActionBar> <TableActionBarButtonCreateCommentThreadCompany />
<TableActionBarButtonCreateCommentThreadCompany /> <TableActionBarButtonDeleteCompanies />
<TableActionBarButtonDeleteCompanies /> </EntityTableActionBar>
</EntityTableActionBar> </>
</> </WithTopBarContainer>
</WithTopBarContainer>
</AppPage>
); );
} }

View File

@ -3,11 +3,11 @@ import type { Meta } from '@storybook/react';
import { userEvent, within } from '@storybook/testing-library'; import { userEvent, within } from '@storybook/testing-library';
import { graphqlMocks } from '~/testing/graphqlMocks'; import { graphqlMocks } from '~/testing/graphqlMocks';
import { getRenderWrapperForPage } from '~/testing/renderWrappers';
import { Companies } from '../Companies'; import { Companies } from '../Companies';
import { Story } from './Companies.stories'; import { Story } from './Companies.stories';
import { render } from './shared';
const meta: Meta<typeof Companies> = { const meta: Meta<typeof Companies> = {
title: 'Pages/Companies/Comments', title: 'Pages/Companies/Comments',
@ -17,7 +17,7 @@ const meta: Meta<typeof Companies> = {
export default meta; export default meta;
export const OpenCommentsSection: Story = { export const OpenCommentsSection: Story = {
render, render: getRenderWrapperForPage(<Companies />, '/companies'),
play: async ({ canvasElement }) => { play: async ({ canvasElement }) => {
const canvas = within(canvasElement); const canvas = within(canvasElement);

View File

@ -2,10 +2,12 @@ import { expect } from '@storybook/jest';
import type { Meta } from '@storybook/react'; import type { Meta } from '@storybook/react';
import { userEvent, within } from '@storybook/testing-library'; import { userEvent, within } from '@storybook/testing-library';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { getRenderWrapperForPage } from '~/testing/renderWrappers';
import { Companies } from '../Companies'; import { Companies } from '../Companies';
import { Story } from './Companies.stories'; import { Story } from './Companies.stories';
import { mocks, render } from './shared';
const meta: Meta<typeof Companies> = { const meta: Meta<typeof Companies> = {
title: 'Pages/Companies/FilterBy', title: 'Pages/Companies/FilterBy',
@ -15,7 +17,7 @@ const meta: Meta<typeof Companies> = {
export default meta; export default meta;
export const FilterByName: Story = { export const FilterByName: Story = {
render, render: getRenderWrapperForPage(<Companies />, '/companies'),
play: async ({ canvasElement }) => { play: async ({ canvasElement }) => {
const canvas = within(canvasElement); const canvas = within(canvasElement);
@ -38,12 +40,12 @@ export const FilterByName: Story = {
expect(await canvas.findByText('Contains Air')).toBeInTheDocument(); expect(await canvas.findByText('Contains Air')).toBeInTheDocument();
}, },
parameters: { parameters: {
msw: mocks, msw: graphqlMocks,
}, },
}; };
export const FilterByAccountOwner: Story = { export const FilterByAccountOwner: Story = {
render, render: getRenderWrapperForPage(<Companies />, '/companies'),
play: async ({ canvasElement }) => { play: async ({ canvasElement }) => {
const canvas = within(canvasElement); const canvas = within(canvasElement);
@ -72,6 +74,6 @@ export const FilterByAccountOwner: Story = {
expect(await canvas.findByText('Is Charles Test')).toBeInTheDocument(); expect(await canvas.findByText('Is Charles Test')).toBeInTheDocument();
}, },
parameters: { parameters: {
msw: mocks, msw: graphqlMocks,
}, },
}; };

View File

@ -2,10 +2,12 @@ import { expect } from '@storybook/jest';
import type { Meta } from '@storybook/react'; import type { Meta } from '@storybook/react';
import { userEvent, within } from '@storybook/testing-library'; import { userEvent, within } from '@storybook/testing-library';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { getRenderWrapperForPage } from '~/testing/renderWrappers';
import { Companies } from '../Companies'; import { Companies } from '../Companies';
import { Story } from './Companies.stories'; import { Story } from './Companies.stories';
import { mocks, render } from './shared';
const meta: Meta<typeof Companies> = { const meta: Meta<typeof Companies> = {
title: 'Pages/Companies/SortBy', title: 'Pages/Companies/SortBy',
@ -15,7 +17,7 @@ const meta: Meta<typeof Companies> = {
export default meta; export default meta;
export const SortByName: Story = { export const SortByName: Story = {
render, render: getRenderWrapperForPage(<Companies />, '/companies'),
play: async ({ canvasElement }) => { play: async ({ canvasElement }) => {
const canvas = within(canvasElement); const canvas = within(canvasElement);
@ -41,6 +43,6 @@ export const SortByName: Story = {
await expect(canvas.queryAllByTestId('remove-icon-name')).toStrictEqual([]); await expect(canvas.queryAllByTestId('remove-icon-name')).toStrictEqual([]);
}, },
parameters: { parameters: {
msw: mocks, msw: graphqlMocks,
}, },
}; };

View File

@ -15,7 +15,7 @@ export default meta;
export type Story = StoryObj<typeof Companies>; export type Story = StoryObj<typeof Companies>;
export const Default: Story = { export const Default: Story = {
render: getRenderWrapperForPage(<Companies />), render: getRenderWrapperForPage(<Companies />, '/companies'),
parameters: { parameters: {
msw: graphqlMocks, msw: graphqlMocks,
}, },

View File

@ -1,57 +0,0 @@
import { MemoryRouter } from 'react-router-dom';
import { ApolloProvider } from '@apollo/client';
import { graphql } from 'msw';
import { RecoilRoot } from 'recoil';
import { GraphqlQueryCompany } from '@/companies/interfaces/company.interface';
import { GraphqlQueryUser } from '@/users/interfaces/user.interface';
import { FullHeightStorybookLayout } from '~/testing/FullHeightStorybookLayout';
import { filterAndSortData } from '~/testing/mock-data';
import { mockedCompaniesData } from '~/testing/mock-data/companies';
import { mockedUsersData } from '~/testing/mock-data/users';
import { mockedClient } from '~/testing/mockedClient';
import { Companies } from '../Companies';
export const mocks = [
graphql.query('GetCompanies', (req, res, ctx) => {
const returnedMockedData = filterAndSortData<GraphqlQueryCompany>(
mockedCompaniesData,
req.variables.where,
req.variables.orderBy,
req.variables.limit,
);
return res(
ctx.data({
companies: returnedMockedData,
}),
);
}),
graphql.query('SearchUser', (req, res, ctx) => {
const returnedMockedData = filterAndSortData<GraphqlQueryUser>(
mockedUsersData,
req.variables.where,
req.variables.orderBy,
req.variables.limit,
);
return res(
ctx.data({
searchResults: returnedMockedData,
}),
);
}),
];
export function render() {
return (
<RecoilRoot>
<ApolloProvider client={mockedClient}>
<MemoryRouter>
<FullHeightStorybookLayout>
<Companies />
</FullHeightStorybookLayout>
</MemoryRouter>
</ApolloProvider>
</RecoilRoot>
);
}

View File

@ -1,6 +1,5 @@
import { IconTargetArrow } from '@/ui/icons/index'; import { IconTargetArrow } from '@/ui/icons/index';
import { WithTopBarContainer } from '@/ui/layout/containers/WithTopBarContainer'; import { WithTopBarContainer } from '@/ui/layout/containers/WithTopBarContainer';
import { AppPage } from '~/AppPage';
import { Board } from '../../modules/opportunities/components/Board'; import { Board } from '../../modules/opportunities/components/Board';
import { useBoard } from '../../modules/opportunities/hooks/useBoard'; import { useBoard } from '../../modules/opportunities/hooks/useBoard';
@ -13,10 +12,8 @@ export function Opportunities() {
if (!initialBoard || !items) if (!initialBoard || !items)
return <div>Initial board or items not found</div>; return <div>Initial board or items not found</div>;
return ( return (
<AppPage> <WithTopBarContainer title="Opportunities" icon={<IconTargetArrow />}>
<WithTopBarContainer title="Opportunities" icon={<IconTargetArrow />}> <Board initialBoard={initialBoard} items={items} />
<Board initialBoard={initialBoard} items={items} /> </WithTopBarContainer>
</WithTopBarContainer>
</AppPage>
); );
} }

View File

@ -20,7 +20,6 @@ import { EntityTable } from '@/ui/components/table/EntityTable';
import { IconList, IconUser } from '@/ui/icons/index'; import { IconList, IconUser } from '@/ui/icons/index';
import { WithTopBarContainer } from '@/ui/layout/containers/WithTopBarContainer'; import { WithTopBarContainer } from '@/ui/layout/containers/WithTopBarContainer';
import { BoolExpType } from '@/utils/interfaces/generic.interface'; import { BoolExpType } from '@/utils/interfaces/generic.interface';
import { AppPage } from '~/AppPage';
import { TableActionBarButtonCreateCommentThreadPeople } from './table/TableActionBarButtonCreateCommentThreadPeople'; import { TableActionBarButtonCreateCommentThreadPeople } from './table/TableActionBarButtonCreateCommentThreadPeople';
import { TableActionBarButtonDeletePeople } from './table/TableActionBarButtonDeletePeople'; import { TableActionBarButtonDeletePeople } from './table/TableActionBarButtonDeletePeople';
@ -74,31 +73,29 @@ export function People() {
const theme = useTheme(); const theme = useTheme();
return ( return (
<AppPage> <WithTopBarContainer
<WithTopBarContainer title="People"
title="People" icon={<IconUser size={theme.iconSizeMedium} />}
icon={<IconUser size={theme.iconSizeMedium} />} onAddButtonClick={handleAddButtonClick}
onAddButtonClick={handleAddButtonClick} >
> <>
<> <StyledPeopleContainer>
<StyledPeopleContainer> <EntityTable
<EntityTable data={people}
data={people} columns={peopleColumns}
columns={peopleColumns} viewName="All People"
viewName="All People" viewIcon={<IconList size={theme.iconSizeMedium} />}
viewIcon={<IconList size={theme.iconSizeMedium} />} availableSorts={availableSorts}
availableSorts={availableSorts} availableFilters={availableFilters}
availableFilters={availableFilters} onSortsUpdate={updateSorts}
onSortsUpdate={updateSorts} onFiltersUpdate={updateFilters}
onFiltersUpdate={updateFilters} />
/> </StyledPeopleContainer>
</StyledPeopleContainer> <EntityTableActionBar>
<EntityTableActionBar> <TableActionBarButtonCreateCommentThreadPeople />
<TableActionBarButtonCreateCommentThreadPeople /> <TableActionBarButtonDeletePeople />
<TableActionBarButtonDeletePeople /> </EntityTableActionBar>
</EntityTableActionBar> </>
</> </WithTopBarContainer>
</WithTopBarContainer>
</AppPage>
); );
} }

View File

@ -3,11 +3,11 @@ import type { Meta } from '@storybook/react';
import { userEvent, within } from '@storybook/testing-library'; import { userEvent, within } from '@storybook/testing-library';
import { graphqlMocks } from '~/testing/graphqlMocks'; import { graphqlMocks } from '~/testing/graphqlMocks';
import { getRenderWrapperForPage } from '~/testing/renderWrappers';
import { People } from '../People'; import { People } from '../People';
import { Story } from './People.stories'; import { Story } from './People.stories';
import { render } from './shared';
const meta: Meta<typeof People> = { const meta: Meta<typeof People> = {
title: 'Pages/People/Comments', title: 'Pages/People/Comments',
@ -17,7 +17,7 @@ const meta: Meta<typeof People> = {
export default meta; export default meta;
export const OpenCommentsSection: Story = { export const OpenCommentsSection: Story = {
render, render: getRenderWrapperForPage(<People />, '/people'),
play: async ({ canvasElement }) => { play: async ({ canvasElement }) => {
const canvas = within(canvasElement); const canvas = within(canvasElement);

View File

@ -3,11 +3,11 @@ import type { Meta } from '@storybook/react';
import { userEvent, within } from '@storybook/testing-library'; import { userEvent, within } from '@storybook/testing-library';
import { graphqlMocks } from '~/testing/graphqlMocks'; import { graphqlMocks } from '~/testing/graphqlMocks';
import { getRenderWrapperForPage } from '~/testing/renderWrappers';
import { People } from '../People'; import { People } from '../People';
import { Story } from './People.stories'; import { Story } from './People.stories';
import { render } from './shared';
const meta: Meta<typeof People> = { const meta: Meta<typeof People> = {
title: 'Pages/People/FilterBy', title: 'Pages/People/FilterBy',
@ -17,7 +17,7 @@ const meta: Meta<typeof People> = {
export default meta; export default meta;
export const Email: Story = { export const Email: Story = {
render, render: getRenderWrapperForPage(<People />, '/people'),
play: async ({ canvasElement }) => { play: async ({ canvasElement }) => {
const canvas = within(canvasElement); const canvas = within(canvasElement);
@ -44,7 +44,7 @@ export const Email: Story = {
}; };
export const CompanyName: Story = { export const CompanyName: Story = {
render, render: getRenderWrapperForPage(<People />, '/people'),
play: async ({ canvasElement }) => { play: async ({ canvasElement }) => {
const canvas = within(canvasElement); const canvas = within(canvasElement);

View File

@ -7,12 +7,12 @@ import { GraphqlQueryCompany } from '@/companies/interfaces/company.interface';
import { graphqlMocks } from '~/testing/graphqlMocks'; import { graphqlMocks } from '~/testing/graphqlMocks';
import { fetchOneFromData } from '~/testing/mock-data'; import { fetchOneFromData } from '~/testing/mock-data';
import { mockedPeopleData } from '~/testing/mock-data/people'; import { mockedPeopleData } from '~/testing/mock-data/people';
import { getRenderWrapperForPage } from '~/testing/renderWrappers';
import { sleep } from '~/testing/sleep'; import { sleep } from '~/testing/sleep';
import { People } from '../People'; import { People } from '../People';
import { Story } from './People.stories'; import { Story } from './People.stories';
import { render } from './shared';
const meta: Meta<typeof People> = { const meta: Meta<typeof People> = {
title: 'Pages/People/Input', title: 'Pages/People/Input',
@ -22,7 +22,7 @@ const meta: Meta<typeof People> = {
export default meta; export default meta;
export const InteractWithManyRows: Story = { export const InteractWithManyRows: Story = {
render, render: getRenderWrapperForPage(<People />, '/people'),
play: async ({ canvasElement }) => { play: async ({ canvasElement }) => {
const canvas = within(canvasElement); const canvas = within(canvasElement);
@ -66,7 +66,7 @@ export const InteractWithManyRows: Story = {
}; };
export const CheckCheckboxes: Story = { export const CheckCheckboxes: Story = {
render, render: getRenderWrapperForPage(<People />, '/people'),
play: async ({ canvasElement }) => { play: async ({ canvasElement }) => {
const canvas = within(canvasElement); const canvas = within(canvasElement);
@ -97,7 +97,7 @@ export const CheckCheckboxes: Story = {
}; };
export const EditRelation: Story = { export const EditRelation: Story = {
render, render: getRenderWrapperForPage(<People />, '/people'),
play: async ({ canvasElement }) => { play: async ({ canvasElement }) => {
const canvas = within(canvasElement); const canvas = within(canvasElement);
@ -153,7 +153,7 @@ export const EditRelation: Story = {
}; };
export const SelectRelationWithKeys: Story = { export const SelectRelationWithKeys: Story = {
render, render: getRenderWrapperForPage(<People />, '/people'),
play: async ({ canvasElement }) => { play: async ({ canvasElement }) => {
const canvas = within(canvasElement); const canvas = within(canvasElement);

View File

@ -3,11 +3,11 @@ import type { Meta } from '@storybook/react';
import { userEvent, within } from '@storybook/testing-library'; import { userEvent, within } from '@storybook/testing-library';
import { graphqlMocks } from '~/testing/graphqlMocks'; import { graphqlMocks } from '~/testing/graphqlMocks';
import { getRenderWrapperForPage } from '~/testing/renderWrappers';
import { People } from '../People'; import { People } from '../People';
import { Story } from './People.stories'; import { Story } from './People.stories';
import { render } from './shared';
const meta: Meta<typeof People> = { const meta: Meta<typeof People> = {
title: 'Pages/People/SortBy', title: 'Pages/People/SortBy',
@ -17,7 +17,7 @@ const meta: Meta<typeof People> = {
export default meta; export default meta;
export const Email: Story = { export const Email: Story = {
render, render: getRenderWrapperForPage(<People />, '/people'),
play: async ({ canvasElement }) => { play: async ({ canvasElement }) => {
const canvas = within(canvasElement); const canvas = within(canvasElement);
@ -43,7 +43,7 @@ export const Email: Story = {
}; };
export const Cancel: Story = { export const Cancel: Story = {
render, render: getRenderWrapperForPage(<People />, '/people'),
play: async ({ canvasElement }) => { play: async ({ canvasElement }) => {
const canvas = within(canvasElement); const canvas = within(canvasElement);

View File

@ -1,11 +1,10 @@
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import { graphqlMocks } from '~/testing/graphqlMocks'; import { graphqlMocks } from '~/testing/graphqlMocks';
import { getRenderWrapperForPage } from '~/testing/renderWrappers';
import { People } from '../People'; import { People } from '../People';
import { render } from './shared';
const meta: Meta<typeof People> = { const meta: Meta<typeof People> = {
title: 'Pages/People', title: 'Pages/People',
component: People, component: People,
@ -16,7 +15,7 @@ export default meta;
export type Story = StoryObj<typeof People>; export type Story = StoryObj<typeof People>;
export const Default: Story = { export const Default: Story = {
render, render: getRenderWrapperForPage(<People />, '/people'),
parameters: { parameters: {
msw: graphqlMocks, msw: graphqlMocks,
}, },

View File

@ -1,22 +0,0 @@
import { MemoryRouter } from 'react-router-dom';
import { ApolloProvider } from '@apollo/client';
import { RecoilRoot } from 'recoil';
import { FullHeightStorybookLayout } from '~/testing/FullHeightStorybookLayout';
import { mockedClient } from '~/testing/mockedClient';
import { People } from '../People';
export function render() {
return (
<RecoilRoot>
<ApolloProvider client={mockedClient}>
<MemoryRouter>
<FullHeightStorybookLayout>
<People />
</FullHeightStorybookLayout>
</MemoryRouter>
</ApolloProvider>
</RecoilRoot>
);
}

View File

@ -1,14 +1,20 @@
import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState'; import { currentUserState } from '@/auth/states/currentUserState';
import { SettingsPage } from '@/settings/components/SettingsPage'; import { NoTopBarContainer } from '@/ui/layout/containers/NoTopBarContainer';
import { TopTitle } from '@/ui/layout/top-bar/TopTitle'; import { TopTitle } from '@/ui/layout/top-bar/TopTitle';
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
padding: ${(props) => props.theme.spacing(8)};
`;
export function SettingsProfile() { export function SettingsProfile() {
const currentUser = useRecoilValue(currentUserState); const currentUser = useRecoilValue(currentUserState);
return ( return (
<SettingsPage> <NoTopBarContainer>
<> <StyledContainer>
<TopTitle title="Profile" /> <TopTitle title="Profile" />
<div> <div>
<h5>Name</h5> <h5>Name</h5>
@ -18,7 +24,7 @@ export function SettingsProfile() {
<h5>Email</h5> <h5>Email</h5>
<span>{currentUser?.email} </span> <span>{currentUser?.email} </span>
</div> </div>
</> </StyledContainer>
</SettingsPage> </NoTopBarContainer>
); );
} }

View File

@ -1,11 +1,10 @@
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import { graphqlMocks } from '~/testing/graphqlMocks'; import { graphqlMocks } from '~/testing/graphqlMocks';
import { getRenderWrapperForPage } from '~/testing/renderWrappers';
import { SettingsProfile } from '../SettingsProfile'; import { SettingsProfile } from '../SettingsProfile';
import { render } from './shared';
const meta: Meta<typeof SettingsProfile> = { const meta: Meta<typeof SettingsProfile> = {
title: 'Pages/Settings/SettingsProfile', title: 'Pages/Settings/SettingsProfile',
component: SettingsProfile, component: SettingsProfile,
@ -16,7 +15,7 @@ export default meta;
export type Story = StoryObj<typeof SettingsProfile>; export type Story = StoryObj<typeof SettingsProfile>;
export const Default: Story = { export const Default: Story = {
render, render: getRenderWrapperForPage(<SettingsProfile />, '/settings/profile'),
parameters: { parameters: {
msw: graphqlMocks, msw: graphqlMocks,
}, },

View File

@ -1,22 +0,0 @@
import { MemoryRouter } from 'react-router-dom';
import { ApolloProvider } from '@apollo/client';
import { RecoilRoot } from 'recoil';
import { FullHeightStorybookLayout } from '~/testing/FullHeightStorybookLayout';
import { mockedClient } from '~/testing/mockedClient';
import { SettingsProfile } from '../SettingsProfile';
export function render() {
return (
<RecoilRoot>
<ApolloProvider client={mockedClient}>
<MemoryRouter>
<FullHeightStorybookLayout>
<SettingsProfile />
</FullHeightStorybookLayout>
</MemoryRouter>
</ApolloProvider>
</RecoilRoot>
);
}

View File

@ -3,17 +3,27 @@ import { MemoryRouter } from 'react-router-dom';
import { ApolloProvider } from '@apollo/client'; import { ApolloProvider } from '@apollo/client';
import { RecoilRoot } from 'recoil'; import { RecoilRoot } from 'recoil';
import { DefaultLayout } from '@/ui/layout/DefaultLayout';
import { AuthProvider } from '~/providers/AuthProvider';
import { ComponentStorybookLayout } from './ComponentStorybookLayout'; import { ComponentStorybookLayout } from './ComponentStorybookLayout';
import { FullHeightStorybookLayout } from './FullHeightStorybookLayout'; import { FullHeightStorybookLayout } from './FullHeightStorybookLayout';
import { mockedClient } from './mockedClient'; import { mockedClient } from './mockedClient';
export function getRenderWrapperForPage(children: React.ReactElement) { export function getRenderWrapperForPage(
children: React.ReactElement,
currentPath: string,
) {
return function render() { return function render() {
return ( return (
<RecoilRoot> <RecoilRoot>
<ApolloProvider client={mockedClient}> <ApolloProvider client={mockedClient}>
<MemoryRouter> <MemoryRouter initialEntries={[currentPath]}>
<FullHeightStorybookLayout>{children}</FullHeightStorybookLayout> <FullHeightStorybookLayout>
<AuthProvider>
<DefaultLayout>{children}</DefaultLayout>
</AuthProvider>
</FullHeightStorybookLayout>
</MemoryRouter> </MemoryRouter>
</ApolloProvider> </ApolloProvider>
</RecoilRoot> </RecoilRoot>

View File

@ -12316,10 +12316,10 @@ msw-storybook-addon@^1.8.0:
dependencies: dependencies:
is-node-process "^1.0.1" is-node-process "^1.0.1"
msw@^1.2.1: msw@1.2.1:
version "1.2.2" version "1.2.1"
resolved "https://registry.yarnpkg.com/msw/-/msw-1.2.2.tgz#126c3150c07f651e97b24fbd405821f3aeaf9397" resolved "https://registry.yarnpkg.com/msw/-/msw-1.2.1.tgz#9dd347583eeba5e5c7f33b54be5600a899dc61bd"
integrity sha512-GsW3PE/Es/a1tYThXcM8YHOZ1S1MtivcS3He/LQbbTCx3rbWJYCtWD5XXyJ53KlNPT7O1VI9sCW3xMtgFe8XpQ== integrity sha512-bF7qWJQSmKn6bwGYVPXOxhexTCGD5oJSZg8yt8IBClxvo3Dx/1W0zqE1nX9BSWmzRsCKWfeGWcB/vpqV6aclpw==
dependencies: dependencies:
"@mswjs/cookies" "^0.2.2" "@mswjs/cookies" "^0.2.2"
"@mswjs/interceptors" "^0.17.5" "@mswjs/interceptors" "^0.17.5"