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:
@ -1,56 +1,46 @@
|
|||||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||||
|
|
||||||
import { RequireAuth } from './modules/auth/components/RequireAuth';
|
import { RequireAuth } from './modules/auth/components/RequireAuth';
|
||||||
import { AppLayout } from './modules/ui/layout/AppLayout';
|
|
||||||
import { AuthCallback } from './pages/auth/AuthCallback';
|
import { AuthCallback } from './pages/auth/AuthCallback';
|
||||||
import { Login } from './pages/auth/Login';
|
import { Login } from './pages/auth/Login';
|
||||||
import { Companies } from './pages/companies/Companies';
|
import { Companies } from './pages/companies/Companies';
|
||||||
import { Opportunities } from './pages/opportunities/Opportunities';
|
import { Opportunities } from './pages/opportunities/Opportunities';
|
||||||
import { People } from './pages/people/People';
|
import { People } from './pages/people/People';
|
||||||
|
import { SettingsProfile } from './pages/settings/SettingsProfile';
|
||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
return (
|
return (
|
||||||
<>
|
<Routes>
|
||||||
{
|
<Route
|
||||||
<AppLayout>
|
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>
|
<Routes>
|
||||||
<Route
|
<Route path="callback" element={<AuthCallback />} />
|
||||||
path="/"
|
<Route path="login" element={<Login />} />
|
||||||
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 />} />
|
|
||||||
</Routes>
|
</Routes>
|
||||||
</AppLayout>
|
}
|
||||||
}
|
/>
|
||||||
</>
|
<Route
|
||||||
|
path="settings/*"
|
||||||
|
element={
|
||||||
|
<Routes>
|
||||||
|
<Route path="profile" element={<SettingsProfile />} />
|
||||||
|
</Routes>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Routes>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
64
front/src/AppNavbar.tsx
Normal file
64
front/src/AppNavbar.tsx
Normal 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
11
front/src/AppPage.tsx
Normal 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>;
|
||||||
|
}
|
||||||
@ -50,3 +50,8 @@ export const refreshAccessToken = async () => {
|
|||||||
localStorage.removeItem('accessToken');
|
localStorage.removeItem('accessToken');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const removeTokens = () => {
|
||||||
|
localStorage.removeItem('refreshToken');
|
||||||
|
localStorage.removeItem('accessToken');
|
||||||
|
};
|
||||||
|
|||||||
@ -57,6 +57,14 @@ export const CommandMenu = ({ initiallyOpen = false }) => {
|
|||||||
>
|
>
|
||||||
Companies
|
Companies
|
||||||
</StyledItem>
|
</StyledItem>
|
||||||
|
<StyledItem
|
||||||
|
onSelect={() => {
|
||||||
|
setOpen(false);
|
||||||
|
navigate('/settings/profile');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Settings
|
||||||
|
</StyledItem>
|
||||||
</StyledGroup>
|
</StyledGroup>
|
||||||
</StyledList>
|
</StyledList>
|
||||||
</StyledDialog>
|
</StyledDialog>
|
||||||
|
|||||||
62
front/src/modules/settings/components/SettingsNavbar.tsx
Normal file
62
front/src/modules/settings/components/SettingsNavbar.tsx
Normal 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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
11
front/src/modules/settings/components/SettingsPage.tsx
Normal file
11
front/src/modules/settings/components/SettingsPage.tsx
Normal 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>;
|
||||||
|
}
|
||||||
@ -13,7 +13,7 @@ export const StyledColumn = styled.div`
|
|||||||
export const StyledColumnTitle = styled.h3`
|
export const StyledColumnTitle = styled.h3`
|
||||||
font-family: 'Inter';
|
font-family: 'Inter';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: ${({ theme }) => theme.fontWeightBold};
|
font-weight: ${({ theme }) => theme.fontWeightMedium};
|
||||||
font-size: ${({ theme }) => theme.fontSizeMedium};
|
font-size: ${({ theme }) => theme.fontSizeMedium};
|
||||||
line-height: ${({ theme }) => theme.lineHeight};
|
line-height: ${({ theme }) => theme.lineHeight};
|
||||||
color: ${({ color }) => color};
|
color: ${({ color }) => color};
|
||||||
|
|||||||
@ -108,7 +108,7 @@ const StyledDropdownTopOption = styled.li`
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
color: ${(props) => props.theme.text80};
|
color: ${(props) => props.theme.text80};
|
||||||
font-weight: ${(props) => props.theme.fontWeightBold};
|
font-weight: ${(props) => props.theme.fontWeightMedium};
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: rgba(0, 0, 0, 0.04);
|
background: rgba(0, 0, 0, 0.04);
|
||||||
@ -131,7 +131,7 @@ const StyledSearchField = styled.li`
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
color: ${(props) => props.theme.text60};
|
color: ${(props) => props.theme.text60};
|
||||||
font-weight: ${(props) => props.theme.fontWeightBold};
|
font-weight: ${(props) => props.theme.fontWeightMedium};
|
||||||
border-bottom: var(--wraper-border) solid
|
border-bottom: var(--wraper-border) solid
|
||||||
${(props) => props.theme.primaryBorder};
|
${(props) => props.theme.primaryBorder};
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { useRecoilState, useRecoilValue } from 'recoil';
|
|||||||
import { currentUserState } from '@/auth/states/currentUserState';
|
import { currentUserState } from '@/auth/states/currentUserState';
|
||||||
import { CommandMenu } from '@/search/components/CommandMenu';
|
import { CommandMenu } from '@/search/components/CommandMenu';
|
||||||
|
|
||||||
import { Navbar } from './navbar/Navbar';
|
import { NavbarContainer } from './navbar/NavbarContainer';
|
||||||
import { isNavbarOpenedState } from './states/isNavbarOpenedState';
|
import { isNavbarOpenedState } from './states/isNavbarOpenedState';
|
||||||
import { MOBILE_VIEWPORT } from './styles/themes';
|
import { MOBILE_VIEWPORT } from './styles/themes';
|
||||||
|
|
||||||
@ -35,9 +35,10 @@ const MainContainer = styled.div`
|
|||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
children: JSX.Element;
|
children: JSX.Element;
|
||||||
|
Navbar: () => JSX.Element;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function AppLayout({ children }: OwnProps) {
|
export function DefaultLayout({ children, Navbar }: OwnProps) {
|
||||||
const currentUser = useRecoilState(currentUserState);
|
const currentUser = useRecoilState(currentUserState);
|
||||||
const userIsAuthenticated = !!currentUser;
|
const userIsAuthenticated = !!currentUser;
|
||||||
|
|
||||||
@ -46,7 +47,9 @@ export function AppLayout({ children }: OwnProps) {
|
|||||||
{userIsAuthenticated ? (
|
{userIsAuthenticated ? (
|
||||||
<>
|
<>
|
||||||
<CommandMenu />
|
<CommandMenu />
|
||||||
<Navbar />
|
<NavbarContainer>
|
||||||
|
<Navbar />
|
||||||
|
</NavbarContainer>
|
||||||
<MainContainer>{children}</MainContainer>
|
<MainContainer>{children}</MainContainer>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
69
front/src/modules/ui/layout/SecondaryLayout.tsx
Normal file
69
front/src/modules/ui/layout/SecondaryLayout.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
48
front/src/modules/ui/layout/navbar/NavBackButton.tsx
Normal file
48
front/src/modules/ui/layout/navbar/NavBackButton.tsx
Normal 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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
66
front/src/modules/ui/layout/navbar/NavCollapseButton.tsx
Normal file
66
front/src/modules/ui/layout/navbar/NavCollapseButton.tsx
Normal 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>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -6,13 +6,18 @@ import { MOBILE_VIEWPORT } from '../styles/themes';
|
|||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
label: string;
|
label: string;
|
||||||
to: string;
|
to?: string;
|
||||||
|
onClick?: () => void;
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
icon: ReactNode;
|
icon: ReactNode;
|
||||||
|
danger?: boolean;
|
||||||
|
soon?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StyledItemProps = {
|
type StyledItemProps = {
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
|
danger?: boolean;
|
||||||
|
soon?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const StyledItem = styled.button<StyledItemProps>`
|
const StyledItem = styled.button<StyledItemProps>`
|
||||||
@ -20,19 +25,30 @@ const StyledItem = styled.button<StyledItemProps>`
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
border: none;
|
border: none;
|
||||||
font-size: ${(props) => props.theme.fontSizeMedium};
|
font-size: ${(props) => props.theme.fontSizeMedium};
|
||||||
cursor: pointer;
|
cursor: ${(props) => (props.soon ? 'default' : 'pointer')};
|
||||||
|
pointer-events: ${(props) => (props.soon ? 'none' : 'auto')};
|
||||||
user-select: none;
|
user-select: none;
|
||||||
background: ${(props) => (props.active ? 'rgba(0, 0, 0, 0.04)' : 'inherit')};
|
background: ${(props) => (props.active ? 'rgba(0, 0, 0, 0.04)' : 'inherit')};
|
||||||
padding-top: ${(props) => props.theme.spacing(1)};
|
padding-top: ${(props) => props.theme.spacing(1)};
|
||||||
padding-bottom: ${(props) => props.theme.spacing(1)};
|
padding-bottom: ${(props) => props.theme.spacing(1)};
|
||||||
padding-left: ${(props) => props.theme.spacing(1)};
|
padding-left: ${(props) => props.theme.spacing(1)};
|
||||||
font-family: 'Inter';
|
font-family: 'Inter';
|
||||||
color: ${(props) =>
|
color: ${(props) => {
|
||||||
props.active ? props.theme.text100 : props.theme.text60};
|
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;
|
border-radius: 4px;
|
||||||
:hover {
|
:hover {
|
||||||
background: rgba(0, 0, 0, 0.04);
|
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);
|
margin-bottom: calc(${(props) => props.theme.spacing(1)} / 2);
|
||||||
|
|
||||||
@ -46,19 +62,44 @@ const StyledItemLabel = styled.div`
|
|||||||
margin-left: ${(props) => props.theme.spacing(2)};
|
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 navigate = useNavigate();
|
||||||
|
|
||||||
|
const onItemClick = () => {
|
||||||
|
if (onClick) {
|
||||||
|
onClick();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (to) {
|
||||||
|
navigate(to);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledItem
|
<StyledItem
|
||||||
onClick={() => {
|
onClick={onItemClick}
|
||||||
navigate(to);
|
|
||||||
}}
|
|
||||||
active={active}
|
active={active}
|
||||||
aria-selected={active}
|
aria-selected={active}
|
||||||
|
danger={danger}
|
||||||
|
soon={soon}
|
||||||
>
|
>
|
||||||
{icon}
|
{icon}
|
||||||
<StyledItemLabel>{label}</StyledItemLabel>
|
<StyledItemLabel>{label}</StyledItemLabel>
|
||||||
|
{soon && <StyledSoonPill>Soon</StyledSoonPill>}
|
||||||
</StyledItem>
|
</StyledItem>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
17
front/src/modules/ui/layout/navbar/NavItemsContainer.tsx
Normal file
17
front/src/modules/ui/layout/navbar/NavItemsContainer.tsx
Normal 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;
|
||||||
@ -10,7 +10,7 @@ const StyledTitle = styled.div`
|
|||||||
color: ${(props) => props.theme.text30};
|
color: ${(props) => props.theme.text30};
|
||||||
font-size: ${(props) => props.theme.fontSizeExtraSmall};
|
font-size: ${(props) => props.theme.fontSizeExtraSmall};
|
||||||
font-weight: 600;
|
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-bottom: ${(props) => props.theme.spacing(2)};
|
||||||
padding-left: ${(props) => props.theme.spacing(1)};
|
padding-left: ${(props) => props.theme.spacing(1)};
|
||||||
`;
|
`;
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { currentUserState } from '@/auth/states/currentUserState';
|
import { currentUserState } from '@/auth/states/currentUserState';
|
||||||
import { IconSidebarLeftCollapse } from '@/ui/icons';
|
|
||||||
|
|
||||||
import { isNavbarOpenedState } from '../states/isNavbarOpenedState';
|
import NavCollapseButton from './NavCollapseButton';
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
const StyledContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -47,27 +46,8 @@ const StyledName = styled.div`
|
|||||||
color: ${(props) => props.theme.text80};
|
color: ${(props) => props.theme.text80};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const CollapseButton = styled.button`
|
function NavWorkspaceButton() {
|
||||||
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() {
|
|
||||||
const currentUser = useRecoilValue(currentUserState);
|
const currentUser = useRecoilValue(currentUserState);
|
||||||
const [isNavOpen, setIsNavOpen] = useRecoilState(isNavbarOpenedState);
|
|
||||||
|
|
||||||
const currentWorkspace = currentUser?.workspaceMember?.workspace;
|
const currentWorkspace = currentUser?.workspaceMember?.workspace;
|
||||||
|
|
||||||
@ -81,13 +61,9 @@ function WorkspaceContainer() {
|
|||||||
<StyledLogo logo={currentWorkspace?.logo}></StyledLogo>
|
<StyledLogo logo={currentWorkspace?.logo}></StyledLogo>
|
||||||
<StyledName>{currentWorkspace?.displayName}</StyledName>
|
<StyledName>{currentWorkspace?.displayName}</StyledName>
|
||||||
</LogoAndNameContainer>
|
</LogoAndNameContainer>
|
||||||
{isNavOpen && (
|
<NavCollapseButton />
|
||||||
<CollapseButton onClick={() => setIsNavOpen(!isNavOpen)}>
|
|
||||||
<IconSidebarLeftCollapse size={16} />
|
|
||||||
</CollapseButton>
|
|
||||||
)}
|
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default WorkspaceContainer;
|
export default NavWorkspaceButton;
|
||||||
@ -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>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
62
front/src/modules/ui/layout/navbar/NavbarContainer.tsx
Normal file
62
front/src/modules/ui/layout/navbar/NavbarContainer.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -13,7 +13,9 @@ const commonTheme = {
|
|||||||
iconSizeMedium: '1.08rem',
|
iconSizeMedium: '1.08rem',
|
||||||
iconSizeLarge: '1.23rem',
|
iconSizeLarge: '1.23rem',
|
||||||
|
|
||||||
fontWeightBold: 500,
|
fontWeightMedium: 500,
|
||||||
|
fontWeightSemibold: 600,
|
||||||
|
fontWeightBold: 700,
|
||||||
fontFamily: 'Inter, sans-serif',
|
fontFamily: 'Inter, sans-serif',
|
||||||
lineHeight: '150%',
|
lineHeight: '150%',
|
||||||
|
|
||||||
@ -138,7 +140,7 @@ export const textInputStyle = (props: any) =>
|
|||||||
&::-webkit-input-placeholder {
|
&::-webkit-input-placeholder {
|
||||||
font-family: ${props.theme.fontFamily};
|
font-family: ${props.theme.fontFamily};
|
||||||
color: ${props.theme.text30};
|
color: ${props.theme.text30};
|
||||||
font-weight: ${props.theme.fontWeightBold};
|
font-weight: ${props.theme.fontWeightMedium};
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,8 @@
|
|||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import { TbPlus } from 'react-icons/tb';
|
import { TbPlus } from 'react-icons/tb';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useRecoilState } from 'recoil';
|
|
||||||
|
|
||||||
import { IconSidebarRightCollapse } from '@/ui/icons';
|
import NavCollapseButton from '../navbar/NavCollapseButton';
|
||||||
|
|
||||||
import { isNavbarOpenedState } from '../states/isNavbarOpenedState';
|
|
||||||
|
|
||||||
export const TOP_BAR_MIN_HEIGHT = '40px';
|
export const TOP_BAR_MIN_HEIGHT = '40px';
|
||||||
|
|
||||||
@ -44,24 +41,6 @@ const AddButtonContainer = styled.div`
|
|||||||
margin-right: ${(props) => props.theme.spacing(1)};
|
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 = {
|
type OwnProps = {
|
||||||
title: string;
|
title: string;
|
||||||
icon: ReactNode;
|
icon: ReactNode;
|
||||||
@ -69,16 +48,10 @@ type OwnProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function TopBar({ title, icon, onAddButtonClick }: OwnProps) {
|
export function TopBar({ title, icon, onAddButtonClick }: OwnProps) {
|
||||||
const [isNavOpen, setIsNavOpen] = useRecoilState(isNavbarOpenedState);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TopBarContainer>
|
<TopBarContainer>
|
||||||
{!isNavOpen && (
|
<NavCollapseButton hideIfOpen={true} />
|
||||||
<CollapseButton onClick={() => setIsNavOpen(!isNavOpen)}>
|
|
||||||
<IconSidebarRightCollapse size={16} />
|
|
||||||
</CollapseButton>
|
|
||||||
)}
|
|
||||||
{icon}
|
{icon}
|
||||||
<TitleContainer data-testid="top-bar-title">{title}</TitleContainer>
|
<TitleContainer data-testid="top-bar-title">{title}</TitleContainer>
|
||||||
{onAddButtonClick && (
|
{onAddButtonClick && (
|
||||||
|
|||||||
29
front/src/modules/ui/layout/top-bar/TopTitle.tsx
Normal file
29
front/src/modules/ui/layout/top-bar/TopTitle.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -23,6 +23,7 @@ import { EntityTableActionBar } from '@/ui/components/table/action-bar/EntityTab
|
|||||||
import { EntityTable } from '@/ui/components/table/EntityTable';
|
import { EntityTable } from '@/ui/components/table/EntityTable';
|
||||||
import { WithTopBarContainer } from '@/ui/layout/containers/WithTopBarContainer';
|
import { WithTopBarContainer } from '@/ui/layout/containers/WithTopBarContainer';
|
||||||
import { BoolExpType } from '@/utils/interfaces/generic.interface';
|
import { BoolExpType } from '@/utils/interfaces/generic.interface';
|
||||||
|
import { AppPage } from '~/AppPage';
|
||||||
import { CompanyOrderByWithRelationInput as Companies_Order_By } from '~/generated/graphql';
|
import { CompanyOrderByWithRelationInput as Companies_Order_By } from '~/generated/graphql';
|
||||||
|
|
||||||
import { TableActionBarButtonCreateCommentThreadCompany } from './table/TableActionBarButtonCreateCommentThreadCompany';
|
import { TableActionBarButtonCreateCommentThreadCompany } from './table/TableActionBarButtonCreateCommentThreadCompany';
|
||||||
@ -74,29 +75,31 @@ export function Companies() {
|
|||||||
const companiesColumns = useCompaniesColumns();
|
const companiesColumns = useCompaniesColumns();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WithTopBarContainer
|
<AppPage>
|
||||||
title="Companies"
|
<WithTopBarContainer
|
||||||
icon={<TbBuilding size={16} />}
|
title="Companies"
|
||||||
onAddButtonClick={handleAddButtonClick}
|
icon={<TbBuilding size={16} />}
|
||||||
>
|
onAddButtonClick={handleAddButtonClick}
|
||||||
<>
|
>
|
||||||
<StyledCompaniesContainer>
|
<>
|
||||||
<EntityTable
|
<StyledCompaniesContainer>
|
||||||
data={companies}
|
<EntityTable
|
||||||
columns={companiesColumns}
|
data={companies}
|
||||||
viewName="All Companies"
|
columns={companiesColumns}
|
||||||
viewIcon={<FaList />}
|
viewName="All Companies"
|
||||||
availableSorts={availableSorts}
|
viewIcon={<FaList />}
|
||||||
availableFilters={availableFilters}
|
availableSorts={availableSorts}
|
||||||
onSortsUpdate={updateSorts}
|
availableFilters={availableFilters}
|
||||||
onFiltersUpdate={updateFilters}
|
onSortsUpdate={updateSorts}
|
||||||
/>
|
onFiltersUpdate={updateFilters}
|
||||||
</StyledCompaniesContainer>
|
/>
|
||||||
<EntityTableActionBar>
|
</StyledCompaniesContainer>
|
||||||
<TableActionBarButtonCreateCommentThreadCompany />
|
<EntityTableActionBar>
|
||||||
<TableActionBarButtonDeleteCompanies />
|
<TableActionBarButtonCreateCommentThreadCompany />
|
||||||
</EntityTableActionBar>
|
<TableActionBarButtonDeleteCompanies />
|
||||||
</>
|
</EntityTableActionBar>
|
||||||
</WithTopBarContainer>
|
</>
|
||||||
|
</WithTopBarContainer>
|
||||||
|
</AppPage>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { FaBullseye } from 'react-icons/fa';
|
import { FaBullseye } from 'react-icons/fa';
|
||||||
|
|
||||||
import { WithTopBarContainer } from '@/ui/layout/containers/WithTopBarContainer';
|
import { WithTopBarContainer } from '@/ui/layout/containers/WithTopBarContainer';
|
||||||
|
import { AppPage } from '~/AppPage';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
initialBoard,
|
initialBoard,
|
||||||
@ -10,8 +11,10 @@ import { Board } from '../../modules/opportunities/components/Board';
|
|||||||
|
|
||||||
export function Opportunities() {
|
export function Opportunities() {
|
||||||
return (
|
return (
|
||||||
<WithTopBarContainer title="Opportunities" icon={<FaBullseye />}>
|
<AppPage>
|
||||||
<Board initialBoard={initialBoard} items={items} />
|
<WithTopBarContainer title="Opportunities" icon={<FaBullseye />}>
|
||||||
</WithTopBarContainer>
|
<Board initialBoard={initialBoard} items={items} />
|
||||||
|
</WithTopBarContainer>
|
||||||
|
</AppPage>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import { EntityTableActionBar } from '@/ui/components/table/action-bar/EntityTab
|
|||||||
import { EntityTable } from '@/ui/components/table/EntityTable';
|
import { EntityTable } from '@/ui/components/table/EntityTable';
|
||||||
import { WithTopBarContainer } from '@/ui/layout/containers/WithTopBarContainer';
|
import { WithTopBarContainer } from '@/ui/layout/containers/WithTopBarContainer';
|
||||||
import { BoolExpType } from '@/utils/interfaces/generic.interface';
|
import { BoolExpType } from '@/utils/interfaces/generic.interface';
|
||||||
|
import { AppPage } from '~/AppPage';
|
||||||
|
|
||||||
import { TableActionBarButtonCreateCommentThreadPeople } from './table/TableActionBarButtonCreateCommentThreadPeople';
|
import { TableActionBarButtonCreateCommentThreadPeople } from './table/TableActionBarButtonCreateCommentThreadPeople';
|
||||||
import { TableActionBarButtonDeletePeople } from './table/TableActionBarButtonDeletePeople';
|
import { TableActionBarButtonDeletePeople } from './table/TableActionBarButtonDeletePeople';
|
||||||
@ -72,29 +73,31 @@ export function People() {
|
|||||||
const peopleColumns = usePeopleColumns();
|
const peopleColumns = usePeopleColumns();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WithTopBarContainer
|
<AppPage>
|
||||||
title="People"
|
<WithTopBarContainer
|
||||||
icon={<TbUser size={16} />}
|
title="People"
|
||||||
onAddButtonClick={handleAddButtonClick}
|
icon={<TbUser size={16} />}
|
||||||
>
|
onAddButtonClick={handleAddButtonClick}
|
||||||
<>
|
>
|
||||||
<StyledPeopleContainer>
|
<>
|
||||||
<EntityTable
|
<StyledPeopleContainer>
|
||||||
data={people}
|
<EntityTable
|
||||||
columns={peopleColumns}
|
data={people}
|
||||||
viewName="All People"
|
columns={peopleColumns}
|
||||||
viewIcon={<FaList />}
|
viewName="All People"
|
||||||
availableSorts={availableSorts}
|
viewIcon={<FaList />}
|
||||||
availableFilters={availableFilters}
|
availableSorts={availableSorts}
|
||||||
onSortsUpdate={updateSorts}
|
availableFilters={availableFilters}
|
||||||
onFiltersUpdate={updateFilters}
|
onSortsUpdate={updateSorts}
|
||||||
/>
|
onFiltersUpdate={updateFilters}
|
||||||
</StyledPeopleContainer>
|
/>
|
||||||
<EntityTableActionBar>
|
</StyledPeopleContainer>
|
||||||
<TableActionBarButtonCreateCommentThreadPeople />
|
<EntityTableActionBar>
|
||||||
<TableActionBarButtonDeletePeople />
|
<TableActionBarButtonCreateCommentThreadPeople />
|
||||||
</EntityTableActionBar>
|
<TableActionBarButtonDeletePeople />
|
||||||
</>
|
</EntityTableActionBar>
|
||||||
</WithTopBarContainer>
|
</>
|
||||||
|
</WithTopBarContainer>
|
||||||
|
</AppPage>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
24
front/src/pages/settings/SettingsProfile.tsx
Normal file
24
front/src/pages/settings/SettingsProfile.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
23
front/src/pages/settings/__stories__/settings.stories.tsx
Normal file
23
front/src/pages/settings/__stories__/settings.stories.tsx
Normal 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,
|
||||||
|
},
|
||||||
|
};
|
||||||
22
front/src/pages/settings/__stories__/shared.tsx
Normal file
22
front/src/pages/settings/__stories__/shared.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user