Reorganize frontend and install Craco to alias modules (#190)

This commit is contained in:
Charles Bochet
2023-06-04 11:23:09 +02:00
committed by GitHub
parent bbc80cd543
commit 7b858fd7c9
149 changed files with 3441 additions and 1158 deletions

View File

@ -0,0 +1,40 @@
import { ThemeProvider } from '@emotion/react';
import styled from '@emotion/styled';
import { User } from '@/users/interfaces/user.interface';
import { Navbar } from './navbar/Navbar';
import { lightTheme } from './styles/themes';
const StyledLayout = styled.div`
display: flex;
flex-direction: row;
width: 100vw;
height: 100vh;
background: ${(props) => props.theme.noisyBackground};
position: relative;
`;
const NAVBAR_WIDTH = '236px';
const MainContainer = styled.div`
display: flex;
flex-direction: row;
width: calc(100% - ${NAVBAR_WIDTH});
`;
type OwnProps = {
children: JSX.Element;
user?: User;
};
export function AppLayout({ children, user }: OwnProps) {
return (
<ThemeProvider theme={lightTheme}>
<StyledLayout>
<Navbar user={user} workspace={user?.workspaceMember?.workspace} />
<MainContainer>{children}</MainContainer>
</StyledLayout>
</ThemeProvider>
);
}

View File

@ -0,0 +1,14 @@
import React from 'react';
import styled from '@emotion/styled';
const StyledPanel = styled.div`
display: flex;
background: ${(props) => props.theme.primaryBackground};
border-radius: 8px;
border: 1px solid ${(props) => props.theme.primaryBorder};
width: 100%;
`;
export function Panel({ children }: { children: React.ReactNode }) {
return <StyledPanel>{children}</StyledPanel>;
}

View File

@ -0,0 +1,68 @@
import { ReactNode } from 'react';
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 { TopBar } from '../top-bar/TopBar';
type OwnProps = {
children: JSX.Element;
title: string;
icon: ReactNode;
onAddButtonClick?: () => void;
};
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
width: 100%;
`;
const TOPBAR_HEIGHT = '48px';
const MainContainer = styled.div`
display: flex;
flex-direction: row;
width: calc(100% - ${(props) => props.theme.spacing(3)});
height: calc(100% - ${TOPBAR_HEIGHT} - ${(props) => props.theme.spacing(3)});
background: ${(props) => props.theme.noisyBackground};
padding-right: ${(props) => props.theme.spacing(3)};
padding-bottom: ${(props) => props.theme.spacing(3)};
`;
const RIGHT_DRAWER_WIDTH = '300px';
type LeftContainerProps = {
isRightDrawerOpen?: boolean;
};
const LeftContainer = styled.div<LeftContainerProps>`
display: flex;
width: calc(
100% - ${(props) => (props.isRightDrawerOpen ? RIGHT_DRAWER_WIDTH : '0px')}
);
position: relative;
`;
export function WithTopBarContainer({
children,
title,
icon,
onAddButtonClick,
}: OwnProps) {
const [isRightDrawerOpen] = useRecoilState(isRightDrawerOpenState);
return (
<StyledContainer>
<TopBar title={title} icon={icon} onAddButtonClick={onAddButtonClick} />
<MainContainer>
<LeftContainer isRightDrawerOpen={isRightDrawerOpen}>
<Panel>{children}</Panel>
</LeftContainer>
<RightDrawer />
</MainContainer>
</StyledContainer>
);
}

View File

@ -0,0 +1,60 @@
import { ReactNode } from 'react';
import { useNavigate } from 'react-router-dom';
import styled from '@emotion/styled';
type OwnProps = {
label: string;
to: string;
active?: boolean;
icon: ReactNode;
};
type StyledItemProps = {
active?: boolean;
};
const StyledItem = styled.button<StyledItemProps>`
display: flex;
align-items: center;
border: none;
font-size: ${(props) => props.theme.fontSizeMedium};
cursor: pointer;
user-select: none;
background: ${(props) => (props.active ? 'rgba(0, 0, 0, 0.04)' : 'inherit')};
padding-top: ${(props) => props.theme.spacing(1)};
padding-bottom: ${(props) => props.theme.spacing(1)};
padding-left: ${(props) => props.theme.spacing(1)};
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};
}
margin-bottom: calc(${(props) => props.theme.spacing(1)} / 2);
`;
const StyledItemLabel = styled.div`
display: flex;
margin-left: ${(props) => props.theme.spacing(2)};
`;
function NavItem({ label, icon, to, active }: OwnProps) {
const navigate = useNavigate();
return (
<StyledItem
onClick={() => {
navigate(to);
}}
active={active}
aria-selected={active}
>
{icon}
<StyledItemLabel>{label}</StyledItemLabel>
</StyledItem>
);
}
export default NavItem;

View 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: ${(props) => props.theme.fontSizeExtraSmall};
font-weight: 600;
padding-top: ${(props) => props.theme.spacing(1)};
padding-bottom: ${(props) => props.theme.spacing(2)};
padding-left: ${(props) => props.theme.spacing(1)};
`;
function NavTitle({ label }: OwnProps) {
return <StyledTitle>{label}</StyledTitle>;
}
export default NavTitle;

View File

@ -0,0 +1,64 @@
import { TbBuilding, TbUser } from 'react-icons/tb';
import { useMatch, useResolvedPath } from 'react-router-dom';
import styled from '@emotion/styled';
import { User } from '@/users/interfaces/user.interface';
import { Workspace } from '@/workspaces/interfaces/workspace.interface';
import NavItem from './NavItem';
import NavTitle from './NavTitle';
import WorkspaceContainer from './WorkspaceContainer';
const NavbarContainer = styled.div`
display: flex;
flex-direction: column;
width: 220px;
padding: ${(props) => props.theme.spacing(2)};
flex-shrink: 0;
`;
const NavItemsContainer = styled.div`
display: flex;
flex-direction: column;
margin-top: 40px;
`;
type OwnProps = {
user?: User;
workspace?: Workspace;
};
export function Navbar({ workspace }: OwnProps) {
return (
<>
<NavbarContainer>
{workspace && <WorkspaceContainer workspace={workspace} />}
<NavItemsContainer>
<NavTitle label="Workspace" />
<NavItem
label="People"
to="/people"
icon={<TbUser size={16} />}
active={
!!useMatch({
path: useResolvedPath('/people').pathname,
end: true,
})
}
/>
<NavItem
label="Companies"
to="/companies"
icon={<TbBuilding size={16} />}
active={
!!useMatch({
path: useResolvedPath('/companies').pathname,
end: true,
})
}
/>
</NavItemsContainer>
</NavbarContainer>
</>
);
}

View File

@ -0,0 +1,50 @@
import styled from '@emotion/styled';
import { Workspace } from '@/workspaces/interfaces/workspace.interface';
type OwnProps = {
workspace: Workspace;
};
const StyledContainer = styled.button`
display: inline-flex;
height: 34px;
align-items: center;
cursor: pointer;
user-select: none;
border: 0;
background: inherit;
padding: ${(props) => props.theme.spacing(2)};
margin-left: ${(props) => props.theme.spacing(1)};
align-self: flex-start;
`;
type StyledLogoProps = {
logo?: string | null;
};
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: ${(props) => props.theme.spacing(1)};
font-family: 'Inter';
font-weight: 500;
font-size: ${(props) => props.theme.fontSizeLarge};
`;
function WorkspaceContainer({ workspace }: OwnProps) {
return (
<StyledContainer>
<StyledLogo logo={workspace.logo}></StyledLogo>
<StyledName>{workspace?.displayName}</StyledName>
</StyledContainer>
);
}
export default WorkspaceContainer;

View File

@ -0,0 +1,34 @@
import styled from '@emotion/styled';
import { useRecoilState } from 'recoil';
import { isDefined } from '@/utils/type-guards/isDefined';
import { Panel } from '../../Panel';
import { isRightDrawerOpenState } from '../states/isRightDrawerOpenState';
import { rightDrawerPageState } from '../states/rightDrawerPageState';
import { RightDrawerRouter } from './RightDrawerRouter';
const StyledRightDrawer = styled.div`
display: flex;
flex-direction: row;
width: 300px;
margin-left: ${(props) => props.theme.spacing(2)};
`;
export function RightDrawer() {
const [isRightDrawerOpen] = useRecoilState(isRightDrawerOpenState);
const [rightDrawerPage] = useRecoilState(rightDrawerPageState);
if (!isRightDrawerOpen || !isDefined(rightDrawerPage)) {
return <></>;
}
return (
<StyledRightDrawer>
<Panel>
<RightDrawerRouter />
</Panel>
</StyledRightDrawer>
);
}

View File

@ -0,0 +1,8 @@
import styled from '@emotion/styled';
export const RightDrawerBody = styled.div`
display: flex;
flex-direction: column;
padding: 8px;
`;

View File

@ -0,0 +1,8 @@
import styled from '@emotion/styled';
export const RightDrawerPage = styled.div`
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
`;

View File

@ -0,0 +1,16 @@
import { useRecoilState } from 'recoil';
import { RightDrawerComments } from '@/comments/components/comments/RightDrawerComments';
import { isDefined } from '@/utils/type-guards/isDefined';
import { rightDrawerPageState } from '../states/rightDrawerPageState';
export function RightDrawerRouter() {
const [rightDrawerPage] = useRecoilState(rightDrawerPageState);
if (!isDefined(rightDrawerPage)) {
return <></>;
}
return rightDrawerPage === 'comments' ? <RightDrawerComments /> : <></>;
}

View File

@ -0,0 +1,34 @@
import styled from '@emotion/styled';
import { RightDrawerTopBarCloseButton } from './RightDrawerTopBarCloseButton';
const StyledRightDrawerTopBar = styled.div`
display: flex;
flex-direction: row;
height: 40px;
align-items: center;
justify-content: space-between;
padding-left: 8px;
padding-right: 8px;
font-size: 13px;
color: ${(props) => props.theme.text60};
border-bottom: 1px solid ${(props) => props.theme.lightBorder};
`;
const StyledTopBarTitle = styled.div`
align-items: center;
font-weight: 500;
`;
export function RightDrawerTopBar({
title,
}: {
title: string | null | undefined;
}) {
return (
<StyledRightDrawerTopBar>
<StyledTopBarTitle>{title}</StyledTopBarTitle>
<RightDrawerTopBarCloseButton />
</StyledRightDrawerTopBar>
);
}

View File

@ -0,0 +1,38 @@
import { FaTimes } from 'react-icons/fa';
import styled from '@emotion/styled';
import { useRecoilState } from 'recoil';
import { isRightDrawerOpenState } from '../states/isRightDrawerOpenState';
const StyledButton = styled.button`
height: 24px;
width: 24px;
border: 1px solid ${(props) => props.theme.lightBorder};
background: none;
cursor: pointer;
display: flex;
flex-direction: row;
align-items: center;
padding: 8px;
border-radius: 4px;
transition: ${(props) => props.theme.clickableElementBackgroundTransition};
&:hover {
background: ${(props) => props.theme.clickableElementBackgroundHover};
}
`;
export function RightDrawerTopBarCloseButton() {
const [, setIsRightDrawerOpen] = useRecoilState(isRightDrawerOpenState);
function handleButtonClick() {
setIsRightDrawerOpen(false);
}
return (
<StyledButton onClick={handleButtonClick}>
<FaTimes />
</StyledButton>
);
}

View File

@ -1,4 +1,5 @@
import { useRecoilState } from 'recoil';
import { isRightDrawerOpenState } from '../states/isRightDrawerOpenState';
import { rightDrawerPageState } from '../states/rightDrawerPageState';
import { RightDrawerPage } from '../types/RightDrawerPage';

View File

@ -1,4 +1,5 @@
import { atom } from 'recoil';
import { RightDrawerPage } from '../types/RightDrawerPage';
export const rightDrawerPageState = atom<RightDrawerPage | null>({

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -0,0 +1,130 @@
import { css } from '@emotion/react';
import DarkNoise from './dark-noise.jpg';
import LightNoise from './light-noise.jpg';
const commonTheme = {
fontSizeExtraSmall: '0.85rem',
fontSizeSmall: '0.92rem',
fontSizeMedium: '1rem',
fontSizeLarge: '1.08rem',
iconSizeSmall: '0.92rem',
iconSizeMedium: '1.08rem',
iconSizeLarge: '1.23rem',
fontWeightBold: 500,
fontFamily: 'Inter, sans-serif',
spacing: (multiplicator: number) => `${multiplicator * 4}px`,
table: {
horizontalCellMargin: '8px',
},
borderRadius: '4px',
};
const lightThemeSpecific = {
noisyBackground: `url(${LightNoise.toString()});`,
primaryBackground: '#fff',
secondaryBackground: '#fcfcfc',
tertiaryBackground: '#f5f5f5',
quadraryBackground: '#ebebeb',
pinkBackground: '#ffe5f4',
greenBackground: '#e6fff2',
purpleBackground: '#e0e0ff',
yellowBackground: '#fff2e7',
secondaryBackgroundTransparent: 'rgba(252, 252, 252, 0.8)',
primaryBorder: 'rgba(0, 0, 0, 0.08)',
lightBorder: '#f5f5f5',
clickableElementBackgroundHover: 'rgba(0, 0, 0, 0.04)',
clickableElementBackgroundTransition: 'background 0.1s ease',
text100: '#000',
text80: '#333333',
text60: '#666',
text40: '#999999',
text30: '#b3b3b3',
text20: '#cccccc',
text0: '#fff',
blue: '#1961ed',
pink: '#cc0078',
green: '#1e7e50',
purple: '#1111b7',
yellow: '#cc660a',
red: '#ff2e3f',
blueHighTransparency: 'rgba(25, 97, 237, 0.03)',
blueLowTransparency: 'rgba(25, 97, 237, 0.32)',
};
const darkThemeSpecific: typeof lightThemeSpecific = {
noisyBackground: `url(${DarkNoise.toString()});`,
primaryBackground: '#141414',
secondaryBackground: '#171717',
tertiaryBackground: '#333333',
quadraryBackground: '#444444',
pinkBackground: '#cc0078',
greenBackground: '#1e7e50',
purpleBackground: '#1111b7',
yellowBackground: '#cc660a',
secondaryBackgroundTransparent: 'rgba(23, 23, 23, 0.8)',
clickableElementBackgroundHover: 'rgba(0, 0, 0, 0.04)',
clickableElementBackgroundTransition: 'background 0.1s ease',
primaryBorder: 'rgba(255, 255, 255, 0.08)',
lightBorder: '#222222',
text100: '#ffffff',
text80: '#cccccc',
text60: '#999',
text40: '#666',
text30: '#4c4c4c',
text20: '#333333',
text0: '#000',
blue: '#6895ec',
pink: '#ffe5f4',
green: '#e6fff2',
purple: '#e0e0ff',
yellow: '#fff2e7',
red: '#ff2e3f',
blueHighTransparency: 'rgba(104, 149, 236, 0.03)',
blueLowTransparency: 'rgba(104, 149, 236, 0.32)',
};
export const overlayBackground = (props: any) =>
css`
background: ${props.theme.secondaryBackgroundTransparent};
backdrop-filter: blur(8px);
box-shadow: 0px 3px 12px rgba(0, 0, 0, 0.09);
`;
export const textInputStyle = (props: any) =>
css`
border: none;
outline: none;
background-color: transparent;
&::placeholder,
&::-webkit-input-placeholder {
font-family: ${props.theme.fontFamily};
color: ${props.theme.text30};
font-weight: ${props.theme.fontWeightBold};
}
`;
export const lightTheme = { ...commonTheme, ...lightThemeSpecific };
export const darkTheme = { ...commonTheme, ...darkThemeSpecific };
export type ThemeType = typeof lightTheme;

View File

@ -0,0 +1,63 @@
import { ReactNode } from 'react';
import { TbPlus } from 'react-icons/tb';
import styled from '@emotion/styled';
const TopBarContainer = styled.div`
display: flex;
flex-direction: row;
height: 38px;
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;
display: flex;
width: 100%;
`;
const AddButtonContainer = styled.div`
display: flex;
justify-self: flex-end;
flex-shrink: 0;
border: 1px solid ${(props) => props.theme.primaryBorder};
width: 28px;
height: 28px;
align-items: center;
justify-content: center;
border-radius: 4px;
color: ${(props) => props.theme.text80};
cursor: pointer;
user-select: none;
margin-right: ${(props) => props.theme.spacing(1)};
`;
type OwnProps = {
title: string;
icon: ReactNode;
onAddButtonClick?: () => void;
};
export function TopBar({ title, icon, onAddButtonClick }: OwnProps) {
return (
<>
<TopBarContainer>
{icon}
<TitleContainer data-testid="top-bar-title">{title}</TitleContainer>
{onAddButtonClick && (
<AddButtonContainer
data-testid="add-button"
onClick={onAddButtonClick}
>
<TbPlus size={16} />
</AddButtonContainer>
)}
</TopBarContainer>
</>
);
}