mirror of
https://github.com/twentyhq/twenty.git
synced 2024-12-23 12:02:10 +03:00
Merge pull request #27 from twentyhq/cbo-frontend-crm-start
Implement new UI
This commit is contained in:
commit
693ed5746d
@ -52,6 +52,10 @@ Once this is completed you should have:
|
||||
- server available on: http://localhost:3000/health
|
||||
- postgres: available on http://localhost:5432 that should contain `twenty` database
|
||||
|
||||
### Step 3: IDE setup
|
||||
|
||||
If you are using VSCode, please use the `Dev Containers` extension to open the project in a container. This will allow you to run Visual Studio on top of the docker container. This will allow you to run the project without having to install node on your machine.
|
||||
|
||||
### Note
|
||||
|
||||
If you are using Docker install, make sure to ssh in the docker container during development to execute commands. You can also use `Makefile` to help you
|
||||
|
@ -1,8 +1,8 @@
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter">
|
||||
<style type="text/css">
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Source Sans Pro', sans-serif;
|
||||
font-family: 'Inter', sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
2
front/package-lock.json
generated
2
front/package-lock.json
generated
@ -9,7 +9,7 @@
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.7.5",
|
||||
"@emotion/react": "^11.10.5",
|
||||
"@emotion/react": "^11.10.6",
|
||||
"@emotion/styled": "^11.10.5",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.2.1",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.2.1",
|
||||
|
@ -4,7 +4,7 @@
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.7.5",
|
||||
"@emotion/react": "^11.10.5",
|
||||
"@emotion/react": "^11.10.6",
|
||||
"@emotion/styled": "^11.10.5",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.2.1",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.2.1",
|
||||
@ -49,7 +49,11 @@
|
||||
"overrides": {
|
||||
"react-refresh": "0.14.0"
|
||||
},
|
||||
"jest": {},
|
||||
"jest": {
|
||||
"coveragePathIgnorePatterns" : [
|
||||
".stories.tsx$"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
|
@ -12,7 +12,7 @@
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<link href='https://fonts.googleapis.com/css?family=Source+Sans+Pro' rel='stylesheet' type='text/css'>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
|
||||
<title>Twenty</title>
|
||||
</head>
|
||||
|
File diff suppressed because one or more lines are too long
@ -1,6 +1,6 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Source Sans Pro', sans-serif;
|
||||
font-family: 'Inter';
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
@ -10,6 +10,8 @@ import {
|
||||
createHttpLink,
|
||||
} from '@apollo/client';
|
||||
import { setContext } from '@apollo/client/link/context';
|
||||
import '@emotion/react';
|
||||
import { ThemeType } from './layout/styles/themes';
|
||||
|
||||
const httpLink = createHttpLink({ uri: process.env.REACT_APP_API_URL });
|
||||
|
||||
@ -34,3 +36,8 @@ root.render(
|
||||
</BrowserRouter>
|
||||
</ApolloProvider>,
|
||||
);
|
||||
|
||||
declare module '@emotion/react' {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface Theme extends ThemeType {}
|
||||
}
|
||||
|
5
front/src/interfaces/workspace.interface.ts
Normal file
5
front/src/interfaces/workspace.interface.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export interface Workspace {
|
||||
id: number;
|
||||
name: string;
|
||||
logo: string;
|
||||
}
|
@ -1,24 +1,37 @@
|
||||
import Navbar from './navbar/Navbar';
|
||||
import styled from '@emotion/styled';
|
||||
import { ThemeProvider } from '@emotion/react';
|
||||
import { User } from '../interfaces/user.interface';
|
||||
import { Workspace } from '../interfaces/workspace.interface';
|
||||
import { lightTheme } from './styles/themes';
|
||||
|
||||
const StyledLayout = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-direction: row;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
`;
|
||||
|
||||
const StyledRightContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
type OwnProps = {
|
||||
children: JSX.Element;
|
||||
user?: User;
|
||||
workspace?: Workspace;
|
||||
};
|
||||
|
||||
function AppLayout({ children, user }: OwnProps) {
|
||||
function AppLayout({ children, user, workspace }: OwnProps) {
|
||||
return (
|
||||
<StyledLayout>
|
||||
<Navbar user={user} />
|
||||
<div>{children}</div>
|
||||
</StyledLayout>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<StyledLayout>
|
||||
<Navbar user={user} workspace={workspace} />
|
||||
<StyledRightContainer>{children}</StyledRightContainer>
|
||||
</StyledLayout>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
19
front/src/layout/__stories__/AppLayout.stories.tsx
Normal file
19
front/src/layout/__stories__/AppLayout.stories.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import AppLayout from '../AppLayout';
|
||||
import { ThemeProvider } from '@emotion/react';
|
||||
import { lightTheme } from '../styles/themes';
|
||||
|
||||
export default {
|
||||
title: 'AppLayout',
|
||||
component: AppLayout,
|
||||
};
|
||||
|
||||
export const AppLayoutDefault = () => (
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<MemoryRouter>
|
||||
<AppLayout>
|
||||
<div data-testid="content">Test</div>
|
||||
</AppLayout>
|
||||
</MemoryRouter>
|
||||
</ThemeProvider>
|
||||
);
|
10
front/src/layout/__tests__/AppLayout.test.tsx
Normal file
10
front/src/layout/__tests__/AppLayout.test.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import { AppLayoutDefault } from '../__stories__/AppLayout.stories';
|
||||
|
||||
it('Checks the AppLayout render', () => {
|
||||
const { getByTestId } = render(<AppLayoutDefault />);
|
||||
|
||||
const title = getByTestId('content');
|
||||
expect(title).toHaveTextContent('Test');
|
||||
});
|
@ -1,16 +0,0 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
type OwnProps = {
|
||||
children: JSX.Element;
|
||||
};
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
height: calc(100vh - 60px);
|
||||
`;
|
||||
|
||||
function FullWidthContainer({ children }: OwnProps) {
|
||||
return <StyledContainer>{children}</StyledContainer>;
|
||||
}
|
||||
|
||||
export default FullWidthContainer;
|
45
front/src/layout/containers/WithTopBarContainer.tsx
Normal file
45
front/src/layout/containers/WithTopBarContainer.tsx
Normal file
@ -0,0 +1,45 @@
|
||||
import styled from '@emotion/styled';
|
||||
import TopBar from '../top-bar/TopBar';
|
||||
import { IconProp } from '@fortawesome/fontawesome-svg-core';
|
||||
|
||||
type OwnProps = {
|
||||
children: JSX.Element;
|
||||
title: string;
|
||||
icon: IconProp;
|
||||
};
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
const ContentContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: ${(props) => props.theme.noisyBackground};
|
||||
flex: 1;
|
||||
padding-right: 12px;
|
||||
padding-bottom: 12px;
|
||||
`;
|
||||
|
||||
const ContentSubContainer = styled.div`
|
||||
display: flex;
|
||||
background: ${(props) => props.theme.primaryBackground};
|
||||
border-radius: 8px;
|
||||
border: 1px solid ${(props) => props.theme.primaryBorder};
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
function FullWidthContainer({ children, title, icon }: OwnProps) {
|
||||
return (
|
||||
<StyledContainer>
|
||||
<TopBar title={title} icon={icon} />
|
||||
<ContentContainer>
|
||||
<ContentSubContainer>{children}</ContentSubContainer>
|
||||
</ContentContainer>
|
||||
</StyledContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export default FullWidthContainer;
|
@ -1,40 +1,44 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { IconProp } from '@fortawesome/fontawesome-svg-core';
|
||||
|
||||
type OwnProps = {
|
||||
label: string;
|
||||
to: string;
|
||||
active?: boolean;
|
||||
icon: IconProp;
|
||||
};
|
||||
|
||||
type StyledItemProps = {
|
||||
active?: boolean;
|
||||
};
|
||||
|
||||
const StyledItem = styled.button`
|
||||
const StyledItem = styled.button<StyledItemProps>`
|
||||
display: flex;
|
||||
height: 60px;
|
||||
background: inherit;
|
||||
align-items: center;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
font-size: 16px;
|
||||
margin-bottom: -2px;
|
||||
border: none;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
color: ${(props: StyledItemProps) => (props.active ? 'black' : '#2e3138')};
|
||||
font-weight: ${(props: StyledItemProps) =>
|
||||
props.active ? 'bold' : 'inherit'};
|
||||
border: 0;
|
||||
border-bottom: ${(props: StyledItemProps) =>
|
||||
props.active ? '2px solid black' : '2px solid #eaecee'};
|
||||
&:hover {
|
||||
border-bottom: 2px solid #2e3138;
|
||||
background: ${(props) => (props.active ? 'rgba(0, 0, 0, 0.04)' : 'inherit')};
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
padding-left: 4px;
|
||||
font-family: 'Inter';
|
||||
color: ${(props) =>
|
||||
props.active ? props.theme.text100 : props.theme.text60};
|
||||
border-radius: 4px;
|
||||
:hover {
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
color: ${(props) => props.theme.text100};
|
||||
}
|
||||
`;
|
||||
|
||||
function NavItem({ label, to, active }: OwnProps) {
|
||||
const StyledItemLabel = styled.div`
|
||||
display: flex;
|
||||
margin-left: 8px;
|
||||
`;
|
||||
|
||||
function NavItem({ label, icon, to, active }: OwnProps) {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
@ -45,7 +49,8 @@ function NavItem({ label, to, active }: OwnProps) {
|
||||
active={active}
|
||||
aria-selected={active}
|
||||
>
|
||||
{label}
|
||||
<FontAwesomeIcon icon={icon} />
|
||||
<StyledItemLabel>{label}</StyledItemLabel>
|
||||
</StyledItem>
|
||||
);
|
||||
}
|
||||
|
22
front/src/layout/navbar/NavTitle.tsx
Normal file
22
front/src/layout/navbar/NavTitle.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
type OwnProps = {
|
||||
label: string;
|
||||
};
|
||||
|
||||
const StyledTitle = styled.div`
|
||||
display: flex;
|
||||
text-transform: uppercase;
|
||||
color: ${(props) => props.theme.text30};
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
padding-left: 4px;
|
||||
`;
|
||||
|
||||
function NavTitle({ label }: OwnProps) {
|
||||
return <StyledTitle>{label}</StyledTitle>;
|
||||
}
|
||||
|
||||
export default NavTitle;
|
@ -1,64 +1,62 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useMatch, useResolvedPath } from 'react-router-dom';
|
||||
import { User } from '../../interfaces/user.interface';
|
||||
import { Workspace } from '../../interfaces/workspace.interface';
|
||||
import NavItem from './NavItem';
|
||||
import ProfileContainer from './ProfileContainer';
|
||||
import NavTitle from './NavTitle';
|
||||
import WorkspaceContainer from './WorkspaceContainer';
|
||||
import { faUser } from '@fortawesome/free-regular-svg-icons';
|
||||
import { faBuilding } from '@fortawesome/free-regular-svg-icons';
|
||||
|
||||
const NavbarContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
padding-left: 12px;
|
||||
height: 58px;
|
||||
border-bottom: 2px solid #eaecee;
|
||||
flex-direction: column;
|
||||
background: ${(props) => props.theme.noisyBackground};
|
||||
min-width: 220px;
|
||||
padding: 8px;
|
||||
`;
|
||||
|
||||
const NavItemsContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-direction: column;
|
||||
margin-top: 40px;
|
||||
`;
|
||||
|
||||
type OwnProps = {
|
||||
user?: User;
|
||||
workspace?: Workspace;
|
||||
};
|
||||
|
||||
function Navbar({ user }: OwnProps) {
|
||||
function Navbar({ workspace }: OwnProps) {
|
||||
return (
|
||||
<>
|
||||
<NavbarContainer>
|
||||
{workspace && <WorkspaceContainer workspace={workspace} />}
|
||||
<NavItemsContainer>
|
||||
<NavTitle label="Workspace" />
|
||||
<NavItem
|
||||
label="Inbox"
|
||||
to="/"
|
||||
label="People"
|
||||
to="/people"
|
||||
icon={faUser}
|
||||
active={
|
||||
!!useMatch({
|
||||
path: useResolvedPath('/').pathname,
|
||||
path: useResolvedPath('/people').pathname,
|
||||
end: true,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<NavItem
|
||||
label="Contacts"
|
||||
to="/contacts"
|
||||
label="Companies"
|
||||
to="/companies"
|
||||
icon={faBuilding}
|
||||
active={
|
||||
!!useMatch({
|
||||
path: useResolvedPath('/contacts').pathname,
|
||||
end: true,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<NavItem
|
||||
label="Insights"
|
||||
to="/insights"
|
||||
active={
|
||||
!!useMatch({
|
||||
path: useResolvedPath('/insights').pathname,
|
||||
path: useResolvedPath('/companies').pathname,
|
||||
end: true,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</NavItemsContainer>
|
||||
<ProfileContainer user={user} />
|
||||
</NavbarContainer>
|
||||
</>
|
||||
);
|
||||
|
@ -1,63 +0,0 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { User } from '../../interfaces/user.interface';
|
||||
|
||||
type OwnProps = {
|
||||
user?: User;
|
||||
};
|
||||
|
||||
const StyledContainer = styled.button`
|
||||
display: flex;
|
||||
height: 60px;
|
||||
background: inherit;
|
||||
align-items: center;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
font-size: 14px;
|
||||
margin-bottom: -2px;
|
||||
cursor: pointer;
|
||||
border: 0;
|
||||
`;
|
||||
|
||||
const StyledInfoContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
const StyledEmail = styled.div`
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const StyledAvatar = styled.div`
|
||||
display: flex;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 40px;
|
||||
background: black;
|
||||
font-size: 20px;
|
||||
color: white;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
margin-right: 16px;
|
||||
flex-shrink: 0;
|
||||
`;
|
||||
|
||||
function ProfileContainer({ user }: OwnProps) {
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledAvatar>
|
||||
{user?.first_name
|
||||
.split(' ')
|
||||
.map((n) => n[0])
|
||||
.join('')}
|
||||
</StyledAvatar>
|
||||
<StyledInfoContainer>
|
||||
<StyledEmail>{user?.email}</StyledEmail>
|
||||
</StyledInfoContainer>
|
||||
</StyledContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProfileContainer;
|
51
front/src/layout/navbar/WorkspaceContainer.tsx
Normal file
51
front/src/layout/navbar/WorkspaceContainer.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { Workspace } from '../../interfaces/workspace.interface';
|
||||
|
||||
type OwnProps = {
|
||||
workspace: Workspace;
|
||||
};
|
||||
|
||||
const StyledContainer = styled.button`
|
||||
display: inline-flex;
|
||||
width: min-content;
|
||||
height: 34px;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
border: 0;
|
||||
background: inherit;
|
||||
border: 1px solid ${(props) => props.theme.primaryBorder};
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
margin-left: 4px;
|
||||
`;
|
||||
|
||||
type StyledLogoProps = {
|
||||
logo: string;
|
||||
};
|
||||
|
||||
const StyledLogo = styled.div<StyledLogoProps>`
|
||||
background: url(${(props) => props.logo});
|
||||
background-size: cover;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 2px;
|
||||
`;
|
||||
|
||||
const StyledName = styled.div`
|
||||
margin-left: 4px;
|
||||
font-family: 'Inter';
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
font-color: ${(props) => props.theme.text0};
|
||||
`;
|
||||
|
||||
function ProfileContainer({ workspace }: OwnProps) {
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledLogo logo={workspace.logo}></StyledLogo>
|
||||
<StyledName>{workspace?.name}</StyledName>
|
||||
</StyledContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProfileContainer;
|
@ -1,6 +1,9 @@
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { faUser } from '@fortawesome/free-regular-svg-icons';
|
||||
import { ThemeProvider } from '@emotion/react';
|
||||
|
||||
import NavItem from '../../../layout/navbar/NavItem';
|
||||
import { lightTheme } from '../../styles/themes';
|
||||
|
||||
export default {
|
||||
title: 'NavItem',
|
||||
@ -8,13 +11,17 @@ export default {
|
||||
};
|
||||
|
||||
export const NavItemDefault = () => (
|
||||
<MemoryRouter>
|
||||
<NavItem label="Test" to="/test" />
|
||||
</MemoryRouter>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<MemoryRouter>
|
||||
<NavItem label="Test" to="/test" icon={faUser} />
|
||||
</MemoryRouter>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
export const NavItemActive = () => (
|
||||
<MemoryRouter initialEntries={['/test']}>
|
||||
<NavItem label="Test" to="/test" active={true} />
|
||||
</MemoryRouter>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<MemoryRouter initialEntries={['/test']}>
|
||||
<NavItem label="Test" to="/test" active={true} icon={faUser} />
|
||||
</MemoryRouter>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
File diff suppressed because one or more lines are too long
@ -1,16 +1,16 @@
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import { NavbarOnInsights } from '../__stories__/Navbar.stories';
|
||||
import { NavbarOnCompanies } from '../__stories__/Navbar.stories';
|
||||
|
||||
it('Checks the NavItem renders', () => {
|
||||
const { getByRole } = render(<NavbarOnInsights />);
|
||||
const { getByRole } = render(<NavbarOnCompanies />);
|
||||
|
||||
expect(getByRole('button', { name: 'Insights' })).toHaveAttribute(
|
||||
expect(getByRole('button', { name: 'Companies' })).toHaveAttribute(
|
||||
'aria-selected',
|
||||
'true',
|
||||
);
|
||||
|
||||
expect(getByRole('button', { name: 'Inbox' })).toHaveAttribute(
|
||||
expect(getByRole('button', { name: 'People' })).toHaveAttribute(
|
||||
'aria-selected',
|
||||
'false',
|
||||
);
|
||||
|
55
front/src/layout/styles/themes.ts
Normal file
55
front/src/layout/styles/themes.ts
Normal file
@ -0,0 +1,55 @@
|
||||
export const lightTheme = {
|
||||
noisyBackground:
|
||||
'#fcfcfc url(data:image/png;base64,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);',
|
||||
primaryBackground: '#fff',
|
||||
secondaryBackground: '#fcfcfc',
|
||||
tertiaryBackground: '#f5f5f5',
|
||||
pinkBackground: '#ffe5f4',
|
||||
greenBackground: '#e6fff2',
|
||||
purpleBackground: '#e0e0ff',
|
||||
yellowBackground: '#fff2e7',
|
||||
|
||||
primaryBorder: 'rgba(0, 0, 0, 0.08)',
|
||||
|
||||
text100: '#000',
|
||||
text80: '#333',
|
||||
text60: '#666',
|
||||
text40: '#999',
|
||||
text30: '#b3b3b3',
|
||||
text20: '#ccc',
|
||||
text0: '#fff',
|
||||
|
||||
blue: '#1961ed',
|
||||
pink: '#cc0078',
|
||||
green: '#1e7e50',
|
||||
purple: '#1111b7',
|
||||
yellow: '#cc660a',
|
||||
};
|
||||
|
||||
export const darkTheme = {
|
||||
noisyBackground:
|
||||
'#191919 url(data:image/png;base64,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);',
|
||||
primaryBackground: '#141414',
|
||||
secondaryBackground: '#171717',
|
||||
tertiaryBackground: '#333333',
|
||||
pinkBackground: '#cc0078',
|
||||
greenBackground: '#1e7e50',
|
||||
purpleBackground: '#1111b7',
|
||||
yellowBackground: '#cc660a',
|
||||
|
||||
text100: '#ffffff',
|
||||
text80: '#ccc',
|
||||
text60: '#999',
|
||||
text40: '#666',
|
||||
text30: '#4c4c4c',
|
||||
text20: '#333',
|
||||
text0: '#000',
|
||||
|
||||
blue: '#6895ec',
|
||||
pink: '#ffe5f4',
|
||||
green: '#e6fff2',
|
||||
purple: '#e0e0ff',
|
||||
yellow: '#fff2e7',
|
||||
};
|
||||
|
||||
export type ThemeType = typeof lightTheme;
|
38
front/src/layout/top-bar/TopBar.tsx
Normal file
38
front/src/layout/top-bar/TopBar.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { IconProp } from '@fortawesome/fontawesome-svg-core';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
const TopBarContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 40px;
|
||||
align-items: center;
|
||||
background: ${(props) => props.theme.noisyBackground};
|
||||
padding: 8px;
|
||||
font-size: 14px;
|
||||
color: ${(props) => props.theme.text80};
|
||||
`;
|
||||
|
||||
const TitleContainer = styled.div`
|
||||
font-family: 'Inter';
|
||||
margin-left: 4px;
|
||||
font-size: 14px;
|
||||
`;
|
||||
|
||||
type OwnProps = {
|
||||
title: string;
|
||||
icon: IconProp;
|
||||
};
|
||||
|
||||
function TopBar({ title, icon }: OwnProps) {
|
||||
return (
|
||||
<>
|
||||
<TopBarContainer>
|
||||
<FontAwesomeIcon icon={icon} />
|
||||
<TitleContainer data-testid="top-bar-title">{title}</TitleContainer>
|
||||
</TopBarContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default TopBar;
|
@ -1,9 +0,0 @@
|
||||
function Contacts() {
|
||||
return (
|
||||
<div>
|
||||
<h1>This is the history page</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Contacts;
|
@ -1,9 +0,0 @@
|
||||
function Insights() {
|
||||
return (
|
||||
<div>
|
||||
<h1>This is the insights page</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Insights;
|
12
front/src/pages/companies/Companies.tsx
Normal file
12
front/src/pages/companies/Companies.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
import { faBuilding } from '@fortawesome/free-regular-svg-icons';
|
||||
import WithTopBarContainer from '../../layout/containers/WithTopBarContainer';
|
||||
|
||||
function Companies() {
|
||||
return (
|
||||
<WithTopBarContainer title="Companies" icon={faBuilding}>
|
||||
<></>
|
||||
</WithTopBarContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export default Companies;
|
18
front/src/pages/companies/__stories__/Companies.stories.tsx
Normal file
18
front/src/pages/companies/__stories__/Companies.stories.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import Companies from '../Companies';
|
||||
import { ThemeProvider } from '@emotion/react';
|
||||
import { lightTheme } from '../../../layout/styles/themes';
|
||||
import AppLayout from '../../../layout/AppLayout';
|
||||
|
||||
export default {
|
||||
title: 'Companies',
|
||||
component: Companies,
|
||||
};
|
||||
|
||||
export const CompaniesDefault = () => (
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<MemoryRouter>
|
||||
<Companies />
|
||||
</MemoryRouter>
|
||||
</ThemeProvider>
|
||||
);
|
10
front/src/pages/companies/__tests__/Companies.test.tsx
Normal file
10
front/src/pages/companies/__tests__/Companies.test.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import { CompaniesDefault } from '../__stories__/Companies.stories';
|
||||
|
||||
it('Checks the Companies page render', () => {
|
||||
const { getByTestId } = render(<CompaniesDefault />);
|
||||
|
||||
const title = getByTestId('top-bar-title');
|
||||
expect(title).toHaveTextContent('Companies');
|
||||
});
|
@ -1,18 +0,0 @@
|
||||
import FullWidthContainer from '../../layout/containers/FullWidthContainer';
|
||||
import DiscussionPanel from './discussion-panel/DiscussionPanel';
|
||||
import ListPanel from './list-panel/ListPanel';
|
||||
import PluginPanel from './plugin-panel/PluginPanel';
|
||||
|
||||
function Inbox() {
|
||||
return (
|
||||
<FullWidthContainer>
|
||||
<>
|
||||
<ListPanel />
|
||||
<DiscussionPanel />
|
||||
<PluginPanel />
|
||||
</>
|
||||
</FullWidthContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export default Inbox;
|
@ -1,13 +0,0 @@
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import Inbox from '../Inbox';
|
||||
|
||||
export default {
|
||||
title: 'Inbox',
|
||||
component: Inbox,
|
||||
};
|
||||
|
||||
export const InboxDefault = () => (
|
||||
<MemoryRouter>
|
||||
<Inbox />
|
||||
</MemoryRouter>
|
||||
);
|
@ -1,10 +0,0 @@
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import { InboxDefault } from '../__stories__/Inbox.stories';
|
||||
|
||||
it('Checks the Inbox page render', () => {
|
||||
const { getAllByRole } = render(<InboxDefault />);
|
||||
|
||||
const button = getAllByRole('button');
|
||||
expect(button[0]).toHaveTextContent('Sylvie Vartan');
|
||||
});
|
@ -1,100 +0,0 @@
|
||||
import styled from '@emotion/styled';
|
||||
import Composer from './composer/Composer';
|
||||
import Booking, { BookingEvent } from './events/Booking';
|
||||
import Message, { MessageEvent } from './events/Message';
|
||||
import Note, { NoteEvent } from './events/Note';
|
||||
|
||||
export type Event = BookingEvent | MessageEvent | NoteEvent;
|
||||
|
||||
const StyledPanel = styled.div`
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
const EventsContainer = styled.div`
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
padding: 32px;
|
||||
`;
|
||||
|
||||
const StyledToday = styled.div`
|
||||
font-size: 12px;
|
||||
color: #2e3138;
|
||||
border-bottom: 1px solid #eaecee;
|
||||
margin-top: 32px;
|
||||
padding-bottom: 8px;
|
||||
margin-bottom: 8px;
|
||||
`;
|
||||
|
||||
const ComposerContainer = styled.div`
|
||||
display: flex;
|
||||
padding: 32px;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
`;
|
||||
|
||||
function DiscussionPanel() {
|
||||
return (
|
||||
<StyledPanel>
|
||||
<EventsContainer>
|
||||
<Booking
|
||||
booking={{
|
||||
id: 1,
|
||||
time: 'Wed, Sep 10, 2022',
|
||||
user: 'Georges',
|
||||
nights: 4,
|
||||
guests: 5,
|
||||
price: '756.90$',
|
||||
listing: 'Rochefort Montagne',
|
||||
dateRange: 'Mon, Sep 30 - Fri, Oct 2',
|
||||
}}
|
||||
/>
|
||||
<StyledToday>Today</StyledToday>
|
||||
<Message
|
||||
message={{
|
||||
id: 1,
|
||||
time: '2 hours ago',
|
||||
user: 'Georges Alain',
|
||||
channel: 'sms',
|
||||
message:
|
||||
'I’m looking for my order but couldn’t find it. Could you help me find it. I don’t know where to look for.',
|
||||
}}
|
||||
/>
|
||||
<Message
|
||||
message={{
|
||||
id: 2,
|
||||
time: 'just now',
|
||||
user: 'Support',
|
||||
channel: 'sms',
|
||||
message: 'Hello I’m here bla bla bla',
|
||||
agent: 'Leslie A',
|
||||
}}
|
||||
/>
|
||||
<Note
|
||||
note={{
|
||||
id: 1,
|
||||
time: 'just now',
|
||||
agent: 'LeslieA',
|
||||
message: 'Hello I’m here bla bla bla',
|
||||
}}
|
||||
/>
|
||||
<Message
|
||||
message={{
|
||||
id: 3,
|
||||
time: 'just now',
|
||||
user: 'Georges Alain',
|
||||
channel: 'sms',
|
||||
message: 'Thank you !',
|
||||
}}
|
||||
/>
|
||||
</EventsContainer>
|
||||
<ComposerContainer>
|
||||
<Composer />
|
||||
</ComposerContainer>
|
||||
</StyledPanel>
|
||||
);
|
||||
}
|
||||
|
||||
export default DiscussionPanel;
|
@ -1,8 +0,0 @@
|
||||
import DiscussionPanel from '../DiscussionPanel';
|
||||
|
||||
export default {
|
||||
title: 'DiscussionPanel',
|
||||
component: DiscussionPanel,
|
||||
};
|
||||
|
||||
export const DiscussionPanelDefault = () => <DiscussionPanel />;
|
@ -1,10 +0,0 @@
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import { DiscussionPanelDefault } from '../__stories__/DiscussionPanel.stories';
|
||||
|
||||
it('Checks the discussion panel render', () => {
|
||||
const { getAllByText } = render(<DiscussionPanelDefault />);
|
||||
|
||||
const text = getAllByText('Rochefort Montagne');
|
||||
expect(text).toBeDefined();
|
||||
});
|
@ -1,57 +0,0 @@
|
||||
import styled from '@emotion/styled';
|
||||
import ComposerSwitch from './ComposerSwitch';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 2px solid #000000;
|
||||
border-radius: 12px;
|
||||
`;
|
||||
|
||||
const StyledInputContainer = styled.div`
|
||||
display: flex;
|
||||
padding: 8px;
|
||||
justify-content: center;
|
||||
& > textarea {
|
||||
width: 95%;
|
||||
border: none;
|
||||
outline: none;
|
||||
font-family: 'Source Sans Pro';
|
||||
|
||||
&::placeholder {
|
||||
font-family: 'Source Sans Pro';
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const ActionContainer = styled.div`
|
||||
display: flex;
|
||||
padding: 16px;
|
||||
justify-content: flex-end;
|
||||
`;
|
||||
|
||||
const PrimaryButton = styled.button`
|
||||
background: black;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
padding: 16px 24px;
|
||||
border: 0;
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
function Composer() {
|
||||
return (
|
||||
<StyledContainer>
|
||||
<ComposerSwitch />
|
||||
<StyledInputContainer>
|
||||
<textarea rows={5} placeholder="Type to chat..."></textarea>
|
||||
</StyledInputContainer>
|
||||
<ActionContainer>
|
||||
<PrimaryButton>Reply by SMS</PrimaryButton>
|
||||
</ActionContainer>
|
||||
</StyledContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export default Composer;
|
@ -1,49 +0,0 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-bottom: 2px solid #eaecee;
|
||||
padding: 0px 12px;
|
||||
`;
|
||||
|
||||
const SwitchTab = styled.button`
|
||||
display: flex;
|
||||
border-bottom: 2px solid #eaecee;
|
||||
margin-bottom: -2px;
|
||||
padding: 12px;
|
||||
cursor: pointer;
|
||||
color: #2e3138;
|
||||
background: inherit;
|
||||
border: 0;
|
||||
|
||||
&:hover {
|
||||
border-bottom: 2px solid #2e3138;
|
||||
}
|
||||
`;
|
||||
|
||||
const SwitchTabActive = styled.button`
|
||||
display: flex;
|
||||
border: 0;
|
||||
border-bottom: 2px solid black;
|
||||
margin-bottom: -2px;
|
||||
padding: 12px;
|
||||
cursor: pointer;
|
||||
color: black;
|
||||
font-weight: bold;
|
||||
background: inherit;
|
||||
`;
|
||||
|
||||
function ComposerSwitch() {
|
||||
return (
|
||||
<StyledContainer>
|
||||
<SwitchTabActive>Reply</SwitchTabActive>
|
||||
<SwitchTab>Call</SwitchTab>
|
||||
<SwitchTab>Note</SwitchTab>
|
||||
<SwitchTab>Transfer</SwitchTab>
|
||||
<SwitchTab>Help</SwitchTab>
|
||||
</StyledContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export default ComposerSwitch;
|
@ -1,8 +0,0 @@
|
||||
import Composer from '../Composer';
|
||||
|
||||
export default {
|
||||
title: 'Composer',
|
||||
component: Composer,
|
||||
};
|
||||
|
||||
export const ComposerDefault = () => <Composer />;
|
@ -1,8 +0,0 @@
|
||||
import ComposerSwitch from '../ComposerSwitch';
|
||||
|
||||
export default {
|
||||
title: 'Composer',
|
||||
component: ComposerSwitch,
|
||||
};
|
||||
|
||||
export const ComposerSwithDefault = () => <ComposerSwitch />;
|
@ -1,10 +0,0 @@
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import { ComposerDefault } from '../__stories__/Composer.stories';
|
||||
|
||||
it('Checks the composer render', () => {
|
||||
const { getAllByRole } = render(<ComposerDefault />);
|
||||
|
||||
const button = getAllByRole('button');
|
||||
expect(button[0]).toHaveTextContent('Reply');
|
||||
});
|
@ -1,10 +0,0 @@
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import { ComposerSwithDefault } from '../__stories__/ComposerSwitch.stories';
|
||||
|
||||
it('Checks the composer switch render', () => {
|
||||
const { getAllByRole } = render(<ComposerSwithDefault />);
|
||||
|
||||
const button = getAllByRole('button');
|
||||
expect(button[0]).toHaveTextContent('Reply');
|
||||
});
|
@ -1,80 +0,0 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
export type BookingEvent = {
|
||||
id: number;
|
||||
user: string;
|
||||
time: string;
|
||||
listing: string;
|
||||
nights: number;
|
||||
guests: number;
|
||||
price: string;
|
||||
dateRange: string;
|
||||
};
|
||||
|
||||
type OwnProps = {
|
||||
booking: BookingEvent;
|
||||
};
|
||||
|
||||
const StyledBooking = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
const StyledLabel = styled.div`
|
||||
font-size: 12px;
|
||||
color: #2e3138;
|
||||
margin-bottom: 8px;
|
||||
`;
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
padding: 16px;
|
||||
flex-direction: row;
|
||||
border: 1px solid #000000;
|
||||
border-radius: 12px;
|
||||
`;
|
||||
|
||||
const StyledPicture = styled.div`
|
||||
background: #2e3138;
|
||||
width: 50px;
|
||||
height: 42px;
|
||||
margin-right: 16px;
|
||||
`;
|
||||
|
||||
const StyledContent = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
const StyledListing = styled.div`
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
`;
|
||||
|
||||
const StyledDetails = styled.div`
|
||||
font-size: 12px;
|
||||
color: #2e3138;
|
||||
`;
|
||||
|
||||
function Booking({ booking }: OwnProps) {
|
||||
return (
|
||||
<StyledBooking>
|
||||
<StyledLabel>
|
||||
{booking.time} · {booking.user} booked a trip
|
||||
</StyledLabel>
|
||||
<StyledContainer>
|
||||
<StyledPicture />
|
||||
<StyledContent>
|
||||
<StyledListing>{booking.listing}</StyledListing>
|
||||
<StyledDetails>
|
||||
{booking.dateRange} · {booking.nights} nights · {booking.guests}{' '}
|
||||
guests · {booking.price}
|
||||
</StyledDetails>
|
||||
</StyledContent>
|
||||
</StyledContainer>
|
||||
</StyledBooking>
|
||||
);
|
||||
}
|
||||
|
||||
export default Booking;
|
@ -1,90 +0,0 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
export type MessageEvent = {
|
||||
id: number;
|
||||
user: string;
|
||||
time: string;
|
||||
channel: string;
|
||||
message: string;
|
||||
agent?: string;
|
||||
};
|
||||
|
||||
type OwnProps = {
|
||||
message: MessageEvent;
|
||||
};
|
||||
|
||||
const StyledMessage = styled.div`
|
||||
display: flex;
|
||||
margin-top: 12px;
|
||||
margin-bottom: 20px;
|
||||
`;
|
||||
|
||||
const StyledAvatar = styled.div`
|
||||
display: flex;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 40px;
|
||||
background: black;
|
||||
font-size: 20px;
|
||||
color: white;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
margin-right: 16px;
|
||||
flex-shrink: 0;
|
||||
`;
|
||||
|
||||
const StyledContent = styled.div``;
|
||||
|
||||
const StyledTitle = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const StyledUser = styled.div`
|
||||
font-size: 16px;
|
||||
color: black;
|
||||
font-weight: bold;
|
||||
`;
|
||||
|
||||
const StyledTime = styled.div`
|
||||
margin-left: 8px;
|
||||
font-size: 12px;
|
||||
color: #2e3138;
|
||||
`;
|
||||
|
||||
const StyledAgent = styled.div`
|
||||
text-decoration: underline;
|
||||
margin-left: 4px;
|
||||
font-size: 12px;
|
||||
color: #2e3138;
|
||||
`;
|
||||
|
||||
const StyledDetails = styled.div`
|
||||
margin-top: 8px;
|
||||
`;
|
||||
|
||||
function Message({ message }: OwnProps) {
|
||||
return (
|
||||
<StyledMessage>
|
||||
<StyledAvatar>
|
||||
{message.user
|
||||
.split(' ')
|
||||
.map((n) => n[0])
|
||||
.join('')}
|
||||
</StyledAvatar>
|
||||
<StyledContent>
|
||||
<StyledTitle>
|
||||
<StyledUser>{message.user}</StyledUser>
|
||||
<StyledTime>
|
||||
{message.time} ({message.channel})
|
||||
</StyledTime>
|
||||
{message.agent && <StyledAgent>by {message.agent}</StyledAgent>}
|
||||
</StyledTitle>
|
||||
<StyledDetails>{message.message}</StyledDetails>
|
||||
</StyledContent>
|
||||
</StyledMessage>
|
||||
);
|
||||
}
|
||||
|
||||
export default Message;
|
@ -1,52 +0,0 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
export type NoteEvent = {
|
||||
id: number;
|
||||
time: string;
|
||||
message: string;
|
||||
agent: string;
|
||||
};
|
||||
|
||||
type OwnProps = {
|
||||
note: NoteEvent;
|
||||
};
|
||||
|
||||
const StyledNote = styled.div`
|
||||
display: flex;
|
||||
background: #f8f9fa;
|
||||
border-left: 4px solid black;
|
||||
padding: 8px 20px;
|
||||
flex-direction: column;
|
||||
margin-top: 12px;
|
||||
margin-bottom: 20px;
|
||||
`;
|
||||
|
||||
const StyledLabel = styled.div`
|
||||
font-size: 12px;
|
||||
color: #2e3138;
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
`;
|
||||
|
||||
const StyledAgent = styled.div`
|
||||
text-decoration: underline;
|
||||
margin-left: 4px;
|
||||
font-size: 12px;
|
||||
color: #2e3138;
|
||||
`;
|
||||
|
||||
const StyledMessage = styled.div``;
|
||||
|
||||
function Note({ note }: OwnProps) {
|
||||
return (
|
||||
<StyledNote>
|
||||
<StyledLabel>
|
||||
Internal Note {note.time} <StyledAgent>by {note.agent}</StyledAgent>
|
||||
</StyledLabel>
|
||||
<StyledMessage>{note.message}</StyledMessage>
|
||||
</StyledNote>
|
||||
);
|
||||
}
|
||||
|
||||
export default Note;
|
@ -1,21 +0,0 @@
|
||||
import Booking from '../Booking';
|
||||
|
||||
export default {
|
||||
title: 'DiscussionPanel',
|
||||
component: Booking,
|
||||
};
|
||||
|
||||
export const BookingDefault = () => (
|
||||
<Booking
|
||||
booking={{
|
||||
id: 1,
|
||||
time: 'Wed, Sep 10, 2022',
|
||||
user: 'Georges',
|
||||
nights: 4,
|
||||
guests: 5,
|
||||
price: '756.90$',
|
||||
listing: 'Rochefort Montagne',
|
||||
dateRange: 'Mon, Sep 30 - Fri, Oct 2',
|
||||
}}
|
||||
/>
|
||||
);
|
@ -1,19 +0,0 @@
|
||||
import Message from '../Message';
|
||||
|
||||
export default {
|
||||
title: 'DiscussionPanel',
|
||||
component: Message,
|
||||
};
|
||||
|
||||
export const MessageDefault = () => (
|
||||
<Message
|
||||
message={{
|
||||
id: 1,
|
||||
time: '2 hours ago',
|
||||
user: 'Georges Alain',
|
||||
channel: 'sms',
|
||||
message:
|
||||
'I’m looking for my order but couldn’t find it. Could you help me find it. I don’t know where to look for.',
|
||||
}}
|
||||
/>
|
||||
);
|
@ -1,17 +0,0 @@
|
||||
import Note from '../Note';
|
||||
|
||||
export default {
|
||||
title: 'DiscussionPanel',
|
||||
component: Note,
|
||||
};
|
||||
|
||||
export const NoteDefault = () => (
|
||||
<Note
|
||||
note={{
|
||||
id: 1,
|
||||
time: 'just now',
|
||||
agent: 'LeslieA',
|
||||
message: 'Hello I’m here bla bla bla',
|
||||
}}
|
||||
/>
|
||||
);
|
@ -1,10 +0,0 @@
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import { BookingDefault } from '../__stories__/Booking.stories';
|
||||
|
||||
it('Checks the booking event render', () => {
|
||||
const { getAllByText } = render(<BookingDefault />);
|
||||
|
||||
const text = getAllByText('Rochefort Montagne');
|
||||
expect(text).toBeDefined();
|
||||
});
|
@ -1,10 +0,0 @@
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import { MessageDefault } from '../__stories__/Message.stories';
|
||||
|
||||
it('Checks the booking event render', () => {
|
||||
const { getAllByText } = render(<MessageDefault />);
|
||||
|
||||
const text = getAllByText('Georges Alain');
|
||||
expect(text).toBeDefined();
|
||||
});
|
@ -1,10 +0,0 @@
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import { NoteDefault } from '../__stories__/Note.stories';
|
||||
|
||||
it('Checks the booking event render', () => {
|
||||
const { getAllByText } = render(<NoteDefault />);
|
||||
|
||||
const text = getAllByText('Hello I’m here bla bla bla');
|
||||
expect(text).toBeDefined();
|
||||
});
|
@ -1,50 +0,0 @@
|
||||
import styled from '@emotion/styled';
|
||||
import ListPanelHeader from './ListPanelHeader';
|
||||
import ListPanelItem from './ListPanelItem';
|
||||
|
||||
const StyledList = styled.div`
|
||||
display: flex;
|
||||
width: 325px;
|
||||
flex-direction: column;
|
||||
border-right: 2px solid #eaecee;
|
||||
`;
|
||||
|
||||
export type Task = {
|
||||
id: number;
|
||||
targetUser: string;
|
||||
label: string;
|
||||
time: string;
|
||||
lastMessage: string;
|
||||
};
|
||||
|
||||
function ListPanel() {
|
||||
const tasks: Task[] = [
|
||||
{
|
||||
id: 1,
|
||||
targetUser: 'Sylvie Vartan',
|
||||
label: 'Guest at #xxx property',
|
||||
time: '3h',
|
||||
lastMessage:
|
||||
'I’m looking for my order but couldn’t find it. Could you help me find it. I don’t know where ...',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
targetUser: 'Johnny Halliday',
|
||||
label: 'Guest at #xxx property',
|
||||
time: '4h',
|
||||
lastMessage: 'Hello, this is Johnny',
|
||||
},
|
||||
];
|
||||
return (
|
||||
<StyledList>
|
||||
<>
|
||||
<ListPanelHeader />
|
||||
{tasks.map((item) => (
|
||||
<ListPanelItem key={item.id} task={item} />
|
||||
))}
|
||||
</>
|
||||
</StyledList>
|
||||
);
|
||||
}
|
||||
|
||||
export default ListPanel;
|
@ -1,17 +0,0 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const StyledHeader = styled.div`
|
||||
display: flex;
|
||||
font-weight: bold;
|
||||
align-items: center;
|
||||
padding: 16px 24px;
|
||||
font-size: 18px;
|
||||
letter-spacing: 1px;
|
||||
border-bottom: 1px solid #eaecee;
|
||||
`;
|
||||
|
||||
function ListPanelHeader() {
|
||||
return <StyledHeader>6 tasks waiting</StyledHeader>;
|
||||
}
|
||||
|
||||
export default ListPanelHeader;
|
@ -1,98 +0,0 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { Task } from './ListPanel';
|
||||
|
||||
type OwnProps = {
|
||||
task: Task;
|
||||
};
|
||||
|
||||
const StyledListItem = styled.button`
|
||||
display: flex;
|
||||
padding: 16px 24px;
|
||||
flex-direction: column;
|
||||
color: #2e3138;
|
||||
border: 0;
|
||||
border-bottom: 1px solid #eaecee;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
text-align: inherit;
|
||||
align-items: inherit;
|
||||
background: #f1f3f5;
|
||||
`;
|
||||
|
||||
const StyledHeader = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
`;
|
||||
|
||||
const StyledAvatarAndTitle = styled.div`
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const StyledAvatar = styled.div`
|
||||
display: flex;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 40px;
|
||||
background: #52555b;
|
||||
font-size: 20px;
|
||||
color: white;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
`;
|
||||
|
||||
const StyledTitle = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-left: 8px;
|
||||
`;
|
||||
|
||||
const StyledName = styled.div`
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
color: black;
|
||||
`;
|
||||
|
||||
const StyledLabel = styled.div`
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
`;
|
||||
|
||||
const StyledTime = styled.div`
|
||||
display: flex;
|
||||
justify-self: flex-end;
|
||||
color: #7d8187;
|
||||
font-size: 14px;
|
||||
`;
|
||||
|
||||
const StyledContent = styled.div`
|
||||
display: flex;
|
||||
color: #52555b;
|
||||
font-size: 14px;
|
||||
margin-top: 8px;
|
||||
`;
|
||||
|
||||
function ListPanelItem({ task }: OwnProps) {
|
||||
return (
|
||||
<StyledListItem>
|
||||
<StyledHeader>
|
||||
<StyledAvatarAndTitle>
|
||||
<StyledAvatar>
|
||||
{task.targetUser
|
||||
.split(' ')
|
||||
.map((n) => n[0])
|
||||
.join('')}
|
||||
</StyledAvatar>
|
||||
<StyledTitle>
|
||||
<StyledName>{task.targetUser}</StyledName>
|
||||
<StyledLabel>{task.label}</StyledLabel>
|
||||
</StyledTitle>
|
||||
</StyledAvatarAndTitle>
|
||||
<StyledTime>{task.time}</StyledTime>
|
||||
</StyledHeader>
|
||||
<StyledContent>{task.lastMessage} </StyledContent>
|
||||
</StyledListItem>
|
||||
);
|
||||
}
|
||||
|
||||
export default ListPanelItem;
|
@ -1,8 +0,0 @@
|
||||
import ListPanel from '../ListPanel';
|
||||
|
||||
export default {
|
||||
title: 'ListPanel',
|
||||
component: ListPanel,
|
||||
};
|
||||
|
||||
export const ListPanelDefault = () => <ListPanel />;
|
@ -1,9 +0,0 @@
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import ListPanelHeader from '../ListPanelHeader';
|
||||
|
||||
export default {
|
||||
title: 'ListPanel',
|
||||
component: ListPanelHeader,
|
||||
};
|
||||
|
||||
export const ListPanelHeaderDefault = () => <ListPanelHeader />;
|
@ -1,19 +0,0 @@
|
||||
import ListPanelItem from '../ListPanelItem';
|
||||
|
||||
export default {
|
||||
title: 'ListPanel',
|
||||
component: ListPanelItem,
|
||||
};
|
||||
|
||||
export const ListPanelItemDefault = () => (
|
||||
<ListPanelItem
|
||||
task={{
|
||||
id: 1,
|
||||
targetUser: 'Sylvie Vartan',
|
||||
label: 'Guest at #xxx property',
|
||||
time: '3h',
|
||||
lastMessage:
|
||||
'I’m looking for my order but couldn’t find it. Could you help me find it. I don’t know where ...',
|
||||
}}
|
||||
/>
|
||||
);
|
@ -1,10 +0,0 @@
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import { ListPanelDefault } from '../__stories__/ListPanel.stories';
|
||||
|
||||
it('Checks the task list render', () => {
|
||||
const { getAllByRole } = render(<ListPanelDefault />);
|
||||
|
||||
const button = getAllByRole('button');
|
||||
expect(button[0]).toHaveTextContent('Sylvie Vartan');
|
||||
});
|
@ -1,10 +0,0 @@
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import { ListPanelHeaderDefault } from '../__stories__/ListPanelHeader.stories';
|
||||
|
||||
it('Checks the ListPanelHeader render', () => {
|
||||
const { getAllByText } = render(<ListPanelHeaderDefault />);
|
||||
|
||||
const text = getAllByText('6 tasks waiting');
|
||||
expect(text).toBeDefined();
|
||||
});
|
@ -1,10 +0,0 @@
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import { ListPanelItemDefault } from '../__stories__/ListPanelItem.stories';
|
||||
|
||||
it('Checks the ListPanelItem render', () => {
|
||||
const { getAllByText } = render(<ListPanelItemDefault />);
|
||||
|
||||
const text = getAllByText('Sylvie Vartan');
|
||||
expect(text).toBeDefined();
|
||||
});
|
@ -1,27 +0,0 @@
|
||||
import styled from '@emotion/styled';
|
||||
import PluginPanelNav from './PluginPanelNav';
|
||||
import PluginHistory from './plugin-history/PanelHistory';
|
||||
|
||||
const StyledPanel = styled.div`
|
||||
display: flex;
|
||||
width: 350px;
|
||||
border-left: 1px solid #eaecee;
|
||||
`;
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
`;
|
||||
|
||||
function PluginPanel() {
|
||||
return (
|
||||
<StyledPanel>
|
||||
<StyledContainer>
|
||||
<PluginHistory />
|
||||
</StyledContainer>
|
||||
<PluginPanelNav />
|
||||
</StyledPanel>
|
||||
);
|
||||
}
|
||||
|
||||
export default PluginPanel;
|
@ -1,46 +0,0 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faClone } from '@fortawesome/free-regular-svg-icons';
|
||||
|
||||
const StyledNav = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 60px;
|
||||
border-left: 1px solid #eaecee;
|
||||
background: #f1f3f5;
|
||||
`;
|
||||
|
||||
const StyledNavItem = styled.div`
|
||||
display: flex;
|
||||
width: 60px;
|
||||
border-bottom: 1px solid #eaecee;
|
||||
padding: 22px;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
function PluginPanelNav() {
|
||||
return (
|
||||
<StyledNav>
|
||||
<StyledNavItem>
|
||||
<FontAwesomeIcon icon={faClone} size="lg" />
|
||||
</StyledNavItem>
|
||||
<StyledNavItem>
|
||||
<FontAwesomeIcon icon={faClone} size="lg" />
|
||||
</StyledNavItem>
|
||||
<StyledNavItem>
|
||||
<FontAwesomeIcon icon={faClone} size="lg" />
|
||||
</StyledNavItem>
|
||||
<StyledNavItem>
|
||||
<FontAwesomeIcon icon={faClone} size="lg" />
|
||||
</StyledNavItem>
|
||||
<StyledNavItem>
|
||||
<FontAwesomeIcon icon={faClone} size="lg" />
|
||||
</StyledNavItem>
|
||||
<StyledNavItem>
|
||||
<FontAwesomeIcon icon={faClone} size="lg" />
|
||||
</StyledNavItem>
|
||||
</StyledNav>
|
||||
);
|
||||
}
|
||||
|
||||
export default PluginPanelNav;
|
@ -1,5 +0,0 @@
|
||||
function PluginHistory() {
|
||||
return <div></div>;
|
||||
}
|
||||
|
||||
export default PluginHistory;
|
@ -1,7 +0,0 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
function UserActivity() {
|
||||
return;
|
||||
}
|
||||
|
||||
export default UserActivity;
|
@ -1,7 +0,0 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
function UserInformation() {
|
||||
return;
|
||||
}
|
||||
|
||||
export default UserInformation;
|
12
front/src/pages/people/People.tsx
Normal file
12
front/src/pages/people/People.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
import { faUser } from '@fortawesome/free-regular-svg-icons';
|
||||
import WithTopBarContainer from '../../layout/containers/WithTopBarContainer';
|
||||
|
||||
function People() {
|
||||
return (
|
||||
<WithTopBarContainer title="People" icon={faUser}>
|
||||
<></>
|
||||
</WithTopBarContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export default People;
|
17
front/src/pages/people/__stories__/People.stories.tsx
Normal file
17
front/src/pages/people/__stories__/People.stories.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import People from '../People';
|
||||
import { ThemeProvider } from '@emotion/react';
|
||||
import { lightTheme } from '../../../layout/styles/themes';
|
||||
|
||||
export default {
|
||||
title: 'People',
|
||||
component: People,
|
||||
};
|
||||
|
||||
export const PeopleDefault = () => (
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<MemoryRouter>
|
||||
<People />
|
||||
</MemoryRouter>
|
||||
</ThemeProvider>
|
||||
);
|
10
front/src/pages/people/__tests__/People.test.tsx
Normal file
10
front/src/pages/people/__tests__/People.test.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import { PeopleDefault } from '../__stories__/People.stories';
|
||||
|
||||
it('Checks the People page render', () => {
|
||||
const { getByTestId } = render(<PeopleDefault />);
|
||||
|
||||
const title = getByTestId('top-bar-title');
|
||||
expect(title).toHaveTextContent('People');
|
||||
});
|
@ -8,10 +8,10 @@ sh: ##
|
||||
@docker-compose exec twenty sh
|
||||
|
||||
front-test: ##
|
||||
@docker-compose exec twenty sh -c "npm run test"
|
||||
@docker-compose exec twenty sh -c "cd front && npm run test"
|
||||
|
||||
front-coverage: ##
|
||||
@docker-compose exec twenty sh -c "npm run coverage"
|
||||
@docker-compose exec twenty sh -c "cd front && npm run coverage"
|
||||
|
||||
front-storybook: ##
|
||||
@docker-compose exec twenty sh -c "npm run storybook"
|
||||
@docker-compose exec twenty sh -c "cd front && npm run storybook"
|
||||
|
@ -1,5 +1,9 @@
|
||||
FROM node:18-alpine as app
|
||||
|
||||
RUN apk update && apk upgrade && \
|
||||
apk add --no-cache bash git openssh && \
|
||||
apk add libc6-compat
|
||||
|
||||
WORKDIR /app
|
||||
COPY ../.. .
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user