Add settings page (#273)

* Add settings page

* Add 'soon' pill and logout

* Refactor components and layout

* Begin improving mobile display

* Add stories and refactor
This commit is contained in:
Félix Malfait 2023-06-13 17:10:57 +02:00 committed by GitHub
parent c20fd458ae
commit b9c41a1dcd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 683 additions and 240 deletions

View File

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

64
front/src/AppNavbar.tsx Normal file
View File

@ -0,0 +1,64 @@
import {
TbBuilding,
TbInbox,
TbSearch,
TbSettings,
TbUser,
} from 'react-icons/tb';
import { useMatch, useResolvedPath } from 'react-router-dom';
import NavItemsContainer from '@/ui/layout/navbar/NavItemsContainer';
import NavItem from './modules/ui/layout/navbar/NavItem';
import NavTitle from './modules/ui/layout/navbar/NavTitle';
import NavWorkspaceButton from './modules/ui/layout/navbar/NavWorkspaceButton';
export function AppNavbar() {
return (
<>
<NavWorkspaceButton />
<NavItemsContainer>
<NavItem
label="Search"
to="/search"
icon={<TbSearch size={16} />}
soon={true}
/>
<NavItem
label="Inbox"
to="/inbox"
icon={<TbInbox size={16} />}
soon={true}
/>
<NavItem
label="Settings"
to="/settings/profile"
icon={<TbSettings size={16} />}
/>
<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>
</>
);
}

11
front/src/AppPage.tsx Normal file
View File

@ -0,0 +1,11 @@
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

@ -50,3 +50,8 @@ export const refreshAccessToken = async () => {
localStorage.removeItem('accessToken');
}
};
export const removeTokens = () => {
localStorage.removeItem('refreshToken');
localStorage.removeItem('accessToken');
};

View File

@ -57,6 +57,14 @@ export const CommandMenu = ({ initiallyOpen = false }) => {
>
Companies
</StyledItem>
<StyledItem
onSelect={() => {
setOpen(false);
navigate('/settings/profile');
}}
>
Settings
</StyledItem>
</StyledGroup>
</StyledList>
</StyledDialog>

View File

@ -0,0 +1,62 @@
import { TbColorSwatch, TbLogout, TbSettings, TbUser } from 'react-icons/tb';
import { useMatch, useResolvedPath } from 'react-router-dom';
import { removeTokens } from '@/auth/services/AuthService';
import NavBackButton from '@/ui/layout/navbar//NavBackButton';
import NavItem from '@/ui/layout/navbar/NavItem';
import NavItemsContainer from '@/ui/layout/navbar/NavItemsContainer';
import NavTitle from '@/ui/layout/navbar/NavTitle';
export function SettingsNavbar() {
return (
<>
<NavBackButton title="Settings" />
<NavItemsContainer>
<NavTitle label="User" />
<NavItem
label="Profile"
to="/settings/profile"
icon={<TbUser size={16} />}
active={
!!useMatch({
path: useResolvedPath('/people').pathname,
end: true,
})
}
/>
<NavItem
label="Experience"
to="/settings/profile/experience"
icon={<TbColorSwatch size={16} />}
soon={true}
active={
!!useMatch({
path: useResolvedPath('/settings/profile/experience').pathname,
end: true,
})
}
/>
<NavTitle label="Workspace" />
<NavItem
label="General"
to="/settings/workspace"
icon={<TbSettings size={16} />}
soon={true}
active={
!!useMatch({
path: useResolvedPath('/settings/workspace').pathname,
end: true,
})
}
/>
<NavTitle label="Other" />
<NavItem
label="Logout"
onClick={removeTokens}
icon={<TbLogout size={16} />}
danger={true}
/>
</NavItemsContainer>
</>
);
}

View File

@ -0,0 +1,11 @@
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

@ -13,7 +13,7 @@ export const StyledColumn = styled.div`
export const StyledColumnTitle = styled.h3`
font-family: 'Inter';
font-style: normal;
font-weight: ${({ theme }) => theme.fontWeightBold};
font-weight: ${({ theme }) => theme.fontWeightMedium};
font-size: ${({ theme }) => theme.fontSizeMedium};
line-height: ${({ theme }) => theme.lineHeight};
color: ${({ color }) => color};

View File

@ -108,7 +108,7 @@ const StyledDropdownTopOption = styled.li`
cursor: pointer;
user-select: none;
color: ${(props) => props.theme.text80};
font-weight: ${(props) => props.theme.fontWeightBold};
font-weight: ${(props) => props.theme.fontWeightMedium};
&:hover {
background: rgba(0, 0, 0, 0.04);
@ -131,7 +131,7 @@ const StyledSearchField = styled.li`
cursor: pointer;
user-select: none;
color: ${(props) => props.theme.text60};
font-weight: ${(props) => props.theme.fontWeightBold};
font-weight: ${(props) => props.theme.fontWeightMedium};
border-bottom: var(--wraper-border) solid
${(props) => props.theme.primaryBorder};

View File

@ -4,7 +4,7 @@ import { useRecoilState, useRecoilValue } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState';
import { CommandMenu } from '@/search/components/CommandMenu';
import { Navbar } from './navbar/Navbar';
import { NavbarContainer } from './navbar/NavbarContainer';
import { isNavbarOpenedState } from './states/isNavbarOpenedState';
import { MOBILE_VIEWPORT } from './styles/themes';
@ -35,9 +35,10 @@ const MainContainer = styled.div`
type OwnProps = {
children: JSX.Element;
Navbar: () => JSX.Element;
};
export function AppLayout({ children }: OwnProps) {
export function DefaultLayout({ children, Navbar }: OwnProps) {
const currentUser = useRecoilState(currentUserState);
const userIsAuthenticated = !!currentUser;
@ -46,7 +47,9 @@ export function AppLayout({ children }: OwnProps) {
{userIsAuthenticated ? (
<>
<CommandMenu />
<Navbar />
<NavbarContainer>
<Navbar />
</NavbarContainer>
<MainContainer>{children}</MainContainer>
</>
) : (

View File

@ -0,0 +1,69 @@
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`
display: flex;
flex-direction: row;
width: 100vw;
height: 100vh;
background: ${(props) => props.theme.noisyBackground};
position: relative;
`;
const MainContainer = styled.div`
overflow: hidden;
display: flex;
flex-direction: row;
width: 100%;
`;
const SubContainer = styled.div`
display: flex;
flex-direction: column;
background: ${(props) => props.theme.primaryBackground};
border-radius: ${(props) => props.theme.spacing(2)};
border: 1px solid ${(props) => props.theme.primaryBorder};
padding: ${(props) => props.theme.spacing(2)};
margin: ${(props) => props.theme.spacing(4)};
width: 100%;
max-width: calc(100vw - 500px);
padding: 32px;
@media (max-width: ${MOBILE_VIEWPORT}px) {
width: 100%;
max-width: none;
}
`;
const SubSubContainer = styled.div`
display: flex;
width: 350px;
flex-direction: column;
gap: 32px;
color: ${(props) => props.theme.text100};
@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,48 @@
import { TbChevronLeft } from 'react-icons/tb';
import { useNavigate } from 'react-router-dom';
import styled from '@emotion/styled';
import NavCollapseButton from './NavCollapseButton';
type OwnProps = {
title: string;
};
const IconAndButtonContainer = styled.button`
display: flex;
flex-direction: row;
align-items: center;
padding: ${(props) => props.theme.spacing(1)};
gap: ${(props) => props.theme.spacing(1)};
font-size: ${(props) => props.theme.fontSizeLarge};
font-weight: ${(props) => props.theme.fontWeightSemibold};
color: ${(props) => props.theme.text60};
border: none;
background: inherit;
cursor: pointer;
width: 100%;
`;
const StyledContainer = styled.div`
display: flex;
flex-direction: row;
justify-content: space-between;
`;
export default function NavBackButton({ title }: OwnProps) {
const navigate = useNavigate();
return (
<>
<StyledContainer>
<IconAndButtonContainer
onClick={() => navigate('/', { replace: true })}
>
<TbChevronLeft strokeWidth={3} />
<span>{title}</span>
</IconAndButtonContainer>
<NavCollapseButton hideOnDesktop={true} />
</StyledContainer>
</>
);
}

View File

@ -0,0 +1,66 @@
import styled from '@emotion/styled';
import { useRecoilState } from 'recoil';
import { IconSidebarLeftCollapse, IconSidebarRightCollapse } from '@/ui/icons';
import { isNavbarOpenedState } from '../states/isNavbarOpenedState';
import { MOBILE_VIEWPORT } from '../styles/themes';
const CollapseButton = styled.button<{ hideOnDesktop: boolean | undefined }>`
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
user-select: none;
border: 0;
background: inherit;
padding: 0;
cursor: pointer;
color: ${(props) => props.theme.text30};
${(props) =>
props.hideOnDesktop &&
`@media (min-width: ${MOBILE_VIEWPORT}px) {
display:none;
}
`}
`;
interface CollapseButtonProps {
hideIfOpen?: boolean;
hideIfClosed?: boolean;
hideOnDesktop?: boolean;
}
export default function NavCollapseButton({
hideIfOpen,
hideOnDesktop,
}: CollapseButtonProps) {
const [isNavOpen, setIsNavOpen] = useRecoilState(isNavbarOpenedState);
return (
<>
{isNavOpen && !hideIfOpen && (
<CollapseButton
onClick={() => setIsNavOpen(!isNavOpen)}
hideOnDesktop={hideOnDesktop}
>
<IconSidebarLeftCollapse size={16} />
</CollapseButton>
)}
{!isNavOpen && (
<CollapseButton
onClick={() => setIsNavOpen(!isNavOpen)}
hideOnDesktop={hideOnDesktop}
>
<IconSidebarRightCollapse size={16} />
</CollapseButton>
)}
</>
);
}

View File

@ -6,13 +6,18 @@ import { MOBILE_VIEWPORT } from '../styles/themes';
type OwnProps = {
label: string;
to: string;
to?: string;
onClick?: () => void;
active?: boolean;
icon: ReactNode;
danger?: boolean;
soon?: boolean;
};
type StyledItemProps = {
active?: boolean;
danger?: boolean;
soon?: boolean;
};
const StyledItem = styled.button<StyledItemProps>`
@ -20,19 +25,30 @@ const StyledItem = styled.button<StyledItemProps>`
align-items: center;
border: none;
font-size: ${(props) => props.theme.fontSizeMedium};
cursor: pointer;
cursor: ${(props) => (props.soon ? 'default' : 'pointer')};
pointer-events: ${(props) => (props.soon ? 'none' : 'auto')};
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};
color: ${(props) => {
if (props.active) {
return props.theme.text100;
}
if (props.danger) {
return props.theme.red;
}
if (props.soon) {
return props.theme.text20;
}
return props.theme.text60;
}};
border-radius: 4px;
:hover {
background: rgba(0, 0, 0, 0.04);
color: ${(props) => props.theme.text100};
color: ${(props) => (props.danger ? props.theme.red : props.theme.text100)};
}
margin-bottom: calc(${(props) => props.theme.spacing(1)} / 2);
@ -46,19 +62,44 @@ const StyledItemLabel = styled.div`
margin-left: ${(props) => props.theme.spacing(2)};
`;
function NavItem({ label, icon, to, active }: OwnProps) {
const StyledSoonPill = styled.div`
display: flex;
justify-content: center;
align-items: center;
border-radius: 50px;
background-color: rgba(0, 0, 0, 0.04);
font-size: ${(props) => props.theme.fontSizeExtraSmall};
padding: ${(props) => props.theme.spacing(1)}
${(props) => props.theme.spacing(2)} ${(props) => props.theme.spacing(1)}
${(props) => props.theme.spacing(2)};
margin-left: auto; // this aligns the pill to the right
`;
function NavItem({ label, icon, to, onClick, active, danger, soon }: OwnProps) {
const navigate = useNavigate();
const onItemClick = () => {
if (onClick) {
onClick();
return;
}
if (to) {
navigate(to);
return;
}
};
return (
<StyledItem
onClick={() => {
navigate(to);
}}
onClick={onItemClick}
active={active}
aria-selected={active}
danger={danger}
soon={soon}
>
{icon}
<StyledItemLabel>{label}</StyledItemLabel>
{soon && <StyledSoonPill>Soon</StyledSoonPill>}
</StyledItem>
);
}

View File

@ -0,0 +1,17 @@
import styled from '@emotion/styled';
type OwnProps = {
children: React.ReactNode;
};
const StyledNavItemsContainer = styled.div`
display: flex;
flex-direction: column;
margin-top: 40px;
`;
function NavItemsContainer({ children }: OwnProps) {
return <StyledNavItemsContainer>{children}</StyledNavItemsContainer>;
}
export default NavItemsContainer;

View File

@ -10,7 +10,7 @@ const StyledTitle = styled.div`
color: ${(props) => props.theme.text30};
font-size: ${(props) => props.theme.fontSizeExtraSmall};
font-weight: 600;
padding-top: ${(props) => props.theme.spacing(1)};
padding-top: ${(props) => props.theme.spacing(8)};
padding-bottom: ${(props) => props.theme.spacing(2)};
padding-left: ${(props) => props.theme.spacing(1)};
`;

View File

@ -1,10 +1,9 @@
import styled from '@emotion/styled';
import { useRecoilState, useRecoilValue } from 'recoil';
import { useRecoilValue } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState';
import { IconSidebarLeftCollapse } from '@/ui/icons';
import { isNavbarOpenedState } from '../states/isNavbarOpenedState';
import NavCollapseButton from './NavCollapseButton';
const StyledContainer = styled.div`
display: flex;
@ -47,27 +46,8 @@ const StyledName = styled.div`
color: ${(props) => props.theme.text80};
`;
const CollapseButton = styled.button`
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
user-select: none;
border: 0;
background: inherit;
padding: 0;
cursor: pointer;
color: ${(props) => props.theme.text30};
`;
function WorkspaceContainer() {
function NavWorkspaceButton() {
const currentUser = useRecoilValue(currentUserState);
const [isNavOpen, setIsNavOpen] = useRecoilState(isNavbarOpenedState);
const currentWorkspace = currentUser?.workspaceMember?.workspace;
@ -81,13 +61,9 @@ function WorkspaceContainer() {
<StyledLogo logo={currentWorkspace?.logo}></StyledLogo>
<StyledName>{currentWorkspace?.displayName}</StyledName>
</LogoAndNameContainer>
{isNavOpen && (
<CollapseButton onClick={() => setIsNavOpen(!isNavOpen)}>
<IconSidebarLeftCollapse size={16} />
</CollapseButton>
)}
<NavCollapseButton />
</StyledContainer>
);
}
export default WorkspaceContainer;
export default NavWorkspaceButton;

View File

@ -1,72 +0,0 @@
import { TbBuilding, TbUser } from 'react-icons/tb';
import { useMatch, useResolvedPath } from 'react-router-dom';
import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
import { isNavbarOpenedState } from '../states/isNavbarOpenedState';
import { MOBILE_VIEWPORT } from '../styles/themes';
import NavItem from './NavItem';
import NavTitle from './NavTitle';
import WorkspaceContainer from './WorkspaceContainer';
const NavbarContent = styled.div`
display: ${() => (useRecoilValue(isNavbarOpenedState) ? 'block' : 'none')};
`;
const NavbarContainer = styled.div`
flex-direction: column;
width: ${() => (useRecoilValue(isNavbarOpenedState) ? '220px' : '0')};
padding: ${(props) => props.theme.spacing(2)};
flex-shrink: 0;
overflow: hidden;
@media (max-width: ${MOBILE_VIEWPORT}px) {
width: ${(props) =>
useRecoilValue(isNavbarOpenedState)
? `calc(100% - ` + props.theme.spacing(4) + `)`
: '0'};
`;
const NavItemsContainer = styled.div`
display: flex;
flex-direction: column;
margin-top: 40px;
`;
export function Navbar() {
return (
<>
<NavbarContainer>
<NavbarContent>
<WorkspaceContainer />
<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>
</NavbarContent>
</NavbarContainer>
</>
);
}

View File

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

View File

@ -13,7 +13,9 @@ const commonTheme = {
iconSizeMedium: '1.08rem',
iconSizeLarge: '1.23rem',
fontWeightBold: 500,
fontWeightMedium: 500,
fontWeightSemibold: 600,
fontWeightBold: 700,
fontFamily: 'Inter, sans-serif',
lineHeight: '150%',
@ -138,7 +140,7 @@ export const textInputStyle = (props: any) =>
&::-webkit-input-placeholder {
font-family: ${props.theme.fontFamily};
color: ${props.theme.text30};
font-weight: ${props.theme.fontWeightBold};
font-weight: ${props.theme.fontWeightMedium};
}
`;

View File

@ -1,11 +1,8 @@
import { ReactNode } from 'react';
import { TbPlus } from 'react-icons/tb';
import styled from '@emotion/styled';
import { useRecoilState } from 'recoil';
import { IconSidebarRightCollapse } from '@/ui/icons';
import { isNavbarOpenedState } from '../states/isNavbarOpenedState';
import NavCollapseButton from '../navbar/NavCollapseButton';
export const TOP_BAR_MIN_HEIGHT = '40px';
@ -44,24 +41,6 @@ const AddButtonContainer = styled.div`
margin-right: ${(props) => props.theme.spacing(1)};
`;
const CollapseButton = styled.button`
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
user-select: none;
border: 0;
background: inherit;
padding: 0;
cursor: pointer;
color: ${(props) => props.theme.text30};
`;
type OwnProps = {
title: string;
icon: ReactNode;
@ -69,16 +48,10 @@ type OwnProps = {
};
export function TopBar({ title, icon, onAddButtonClick }: OwnProps) {
const [isNavOpen, setIsNavOpen] = useRecoilState(isNavbarOpenedState);
return (
<>
<TopBarContainer>
{!isNavOpen && (
<CollapseButton onClick={() => setIsNavOpen(!isNavOpen)}>
<IconSidebarRightCollapse size={16} />
</CollapseButton>
)}
<NavCollapseButton hideIfOpen={true} />
{icon}
<TitleContainer data-testid="top-bar-title">{title}</TitleContainer>
{onAddButtonClick && (

View File

@ -0,0 +1,29 @@
import styled from '@emotion/styled';
import NavCollapseButton from '../navbar/NavCollapseButton';
const TitleAndCollapseContainer = styled.div`
display: flex;
flex-direction: row;
align-items: center;
`;
const TitleContainer = styled.div`
font-size: ${(props) => props.theme.fontSizeLarge};
font-weight: ${(props) => props.theme.fontWeightSemibold};
display: flex;
width: 100%;
`;
type OwnProps = {
title: string;
};
export function TopTitle({ title }: OwnProps) {
return (
<TitleAndCollapseContainer>
<NavCollapseButton hideIfOpen={true} hideOnDesktop={true} />
<TitleContainer data-testid="top-bar-title">{title}</TitleContainer>
</TitleAndCollapseContainer>
);
}

View File

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

View File

@ -1,6 +1,7 @@
import { FaBullseye } from 'react-icons/fa';
import { WithTopBarContainer } from '@/ui/layout/containers/WithTopBarContainer';
import { AppPage } from '~/AppPage';
import {
initialBoard,
@ -10,8 +11,10 @@ import { Board } from '../../modules/opportunities/components/Board';
export function Opportunities() {
return (
<WithTopBarContainer title="Opportunities" icon={<FaBullseye />}>
<Board initialBoard={initialBoard} items={items} />
</WithTopBarContainer>
<AppPage>
<WithTopBarContainer title="Opportunities" icon={<FaBullseye />}>
<Board initialBoard={initialBoard} items={items} />
</WithTopBarContainer>
</AppPage>
);
}

View File

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

View File

@ -0,0 +1,24 @@
import { useRecoilValue } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState';
import { SettingsPage } from '@/settings/components/SettingsPage';
import { TopTitle } from '@/ui/layout/top-bar/TopTitle';
export function SettingsProfile() {
const currentUser = useRecoilValue(currentUserState);
return (
<SettingsPage>
<>
<TopTitle title="Profile" />
<div>
<h5>Name</h5>
<span>{currentUser?.displayName} </span>
</div>
<div>
<h5>Email</h5>
<span>{currentUser?.email} </span>
</div>
</>
</SettingsPage>
);
}

View File

@ -0,0 +1,23 @@
import type { Meta, StoryObj } from '@storybook/react';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { SettingsProfile } from '../SettingsProfile';
import { render } from './shared';
const meta: Meta<typeof SettingsProfile> = {
title: 'Pages/SettingsProfile',
component: SettingsProfile,
};
export default meta;
export type Story = StoryObj<typeof SettingsProfile>;
export const Default: Story = {
render,
parameters: {
msw: graphqlMocks,
},
};

View File

@ -0,0 +1,22 @@
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>
);
}