feat: add page titles using React Helmet (#1321)
* feat: add page titles using React Helmet * refactor: extract page title logic to separate component * fix: resolve review comments * fix: resolve testing errors
This commit is contained in:
@ -1,8 +1,9 @@
|
||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||
import { Navigate, Route, Routes, useLocation } from 'react-router-dom';
|
||||
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { DefaultLayout } from '@/ui/layout/components/DefaultLayout';
|
||||
import { PageTitle } from '@/ui/utilities/page-title/PageTitle';
|
||||
import { CreateProfile } from '~/pages/auth/CreateProfile';
|
||||
import { CreateWorkspace } from '~/pages/auth/CreateWorkspace';
|
||||
import { SignInUp } from '~/pages/auth/SignInUp';
|
||||
@ -21,13 +22,18 @@ import { Tasks } from '~/pages/tasks/Tasks';
|
||||
import { AppInternalHooks } from '~/sync-hooks/AppInternalHooks';
|
||||
|
||||
import { NotFound } from './pages/not-found/NotFound';
|
||||
import { getPageTitleFromPath } from './utils/title-utils';
|
||||
|
||||
// TEMP FEATURE FLAG FOR VIEW FIELDS
|
||||
export const ACTIVATE_VIEW_FIELDS = true;
|
||||
|
||||
export function App() {
|
||||
const { pathname } = useLocation();
|
||||
const pageTitle = getPageTitleFromPath(pathname);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle title={pageTitle} />
|
||||
<AppInternalHooks />
|
||||
<DefaultLayout>
|
||||
<Routes>
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { HelmetProvider } from 'react-helmet-async';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
@ -24,7 +25,9 @@ const meta: Meta<typeof App> = {
|
||||
<MemoryRouter>
|
||||
<FullHeightStorybookLayout>
|
||||
<MockedAuth>
|
||||
<Story />
|
||||
<HelmetProvider>
|
||||
<Story />
|
||||
</HelmetProvider>
|
||||
</MockedAuth>
|
||||
</FullHeightStorybookLayout>
|
||||
</MemoryRouter>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { StrictMode } from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import { HelmetProvider } from 'react-helmet-async';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
|
||||
@ -27,20 +28,22 @@ root.render(
|
||||
<RecoilRoot>
|
||||
<BrowserRouter>
|
||||
<ApolloProvider>
|
||||
<ClientConfigProvider>
|
||||
<UserProvider>
|
||||
<PageChangeEffect />
|
||||
<AppThemeProvider>
|
||||
<SnackBarProvider>
|
||||
<DialogProvider>
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>
|
||||
</DialogProvider>
|
||||
</SnackBarProvider>
|
||||
</AppThemeProvider>
|
||||
</UserProvider>
|
||||
</ClientConfigProvider>
|
||||
<HelmetProvider>
|
||||
<ClientConfigProvider>
|
||||
<UserProvider>
|
||||
<PageChangeEffect />
|
||||
<AppThemeProvider>
|
||||
<SnackBarProvider>
|
||||
<DialogProvider>
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>
|
||||
</DialogProvider>
|
||||
</SnackBarProvider>
|
||||
</AppThemeProvider>
|
||||
</UserProvider>
|
||||
</ClientConfigProvider>
|
||||
</HelmetProvider>
|
||||
</ApolloProvider>
|
||||
</BrowserRouter>
|
||||
</RecoilRoot>,
|
||||
|
||||
13
front/src/modules/ui/utilities/page-title/PageTitle.tsx
Normal file
13
front/src/modules/ui/utilities/page-title/PageTitle.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
import { Helmet } from 'react-helmet-async';
|
||||
|
||||
type OwnProps = {
|
||||
title: string;
|
||||
};
|
||||
|
||||
export function PageTitle({ title }: OwnProps) {
|
||||
return (
|
||||
<Helmet>
|
||||
<title>{title}</title>
|
||||
</Helmet>
|
||||
);
|
||||
}
|
||||
@ -17,6 +17,7 @@ import { ShowPageLeftContainer } from '@/ui/layout/show-page/components/ShowPage
|
||||
import { ShowPageRightContainer } from '@/ui/layout/show-page/components/ShowPageRightContainer';
|
||||
import { ShowPageSummaryCard } from '@/ui/layout/show-page/components/ShowPageSummaryCard';
|
||||
import { ShowPageRecoilScopeContext } from '@/ui/layout/states/ShowPageRecoilScopeContext';
|
||||
import { PageTitle } from '@/ui/utilities/page-title/PageTitle';
|
||||
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
|
||||
import { useUpdateOneCompanyMutation } from '~/generated/graphql';
|
||||
import { getLogoUrlFromDomainName } from '~/utils';
|
||||
@ -45,66 +46,71 @@ export function CompanyShow() {
|
||||
}
|
||||
|
||||
return (
|
||||
<WithTopBarContainer
|
||||
title={company.name ?? ''}
|
||||
hasBackButton
|
||||
isFavorite={isFavorite}
|
||||
icon={<IconBuildingSkyscraper size={theme.icon.size.md} />}
|
||||
onFavoriteButtonClick={handleFavoriteButtonClick}
|
||||
extraButtons={[
|
||||
<ShowPageAddButton
|
||||
key="add"
|
||||
entity={{
|
||||
id: company.id,
|
||||
type: ActivityTargetableEntityType.Company,
|
||||
}}
|
||||
/>,
|
||||
]}
|
||||
>
|
||||
<RecoilScope SpecificContext={ShowPageRecoilScopeContext}>
|
||||
<ShowPageContainer>
|
||||
<ShowPageLeftContainer>
|
||||
<ShowPageSummaryCard
|
||||
id={company.id}
|
||||
logoOrAvatar={getLogoUrlFromDomainName(company.domainName ?? '')}
|
||||
title={company.name ?? 'No name'}
|
||||
date={company.createdAt ?? ''}
|
||||
renderTitleEditComponent={() => (
|
||||
<CompanyNameEditableField company={company} />
|
||||
)}
|
||||
/>
|
||||
<PropertyBox extraPadding={true}>
|
||||
<EditableFieldMutationContext.Provider
|
||||
value={useUpdateOneCompanyMutation}
|
||||
>
|
||||
<EditableFieldEntityIdContext.Provider value={company.id}>
|
||||
{companyShowFieldDefinition.map((fieldDefinition) => {
|
||||
return (
|
||||
<EditableFieldDefinitionContext.Provider
|
||||
value={fieldDefinition}
|
||||
key={fieldDefinition.id}
|
||||
>
|
||||
<GenericEditableField />
|
||||
</EditableFieldDefinitionContext.Provider>
|
||||
);
|
||||
})}
|
||||
</EditableFieldEntityIdContext.Provider>
|
||||
</EditableFieldMutationContext.Provider>
|
||||
</PropertyBox>
|
||||
<CompanyTeam company={company}></CompanyTeam>
|
||||
</ShowPageLeftContainer>
|
||||
<ShowPageRightContainer
|
||||
<>
|
||||
<PageTitle title={company.name || 'No Name'} />
|
||||
<WithTopBarContainer
|
||||
title={company.name ?? ''}
|
||||
hasBackButton
|
||||
isFavorite={isFavorite}
|
||||
icon={<IconBuildingSkyscraper size={theme.icon.size.md} />}
|
||||
onFavoriteButtonClick={handleFavoriteButtonClick}
|
||||
extraButtons={[
|
||||
<ShowPageAddButton
|
||||
key="add"
|
||||
entity={{
|
||||
id: company.id,
|
||||
type: ActivityTargetableEntityType.Company,
|
||||
}}
|
||||
timeline
|
||||
tasks
|
||||
notes
|
||||
emails
|
||||
/>
|
||||
</ShowPageContainer>
|
||||
</RecoilScope>
|
||||
</WithTopBarContainer>
|
||||
/>,
|
||||
]}
|
||||
>
|
||||
<RecoilScope SpecificContext={ShowPageRecoilScopeContext}>
|
||||
<ShowPageContainer>
|
||||
<ShowPageLeftContainer>
|
||||
<ShowPageSummaryCard
|
||||
id={company.id}
|
||||
logoOrAvatar={getLogoUrlFromDomainName(
|
||||
company.domainName ?? '',
|
||||
)}
|
||||
title={company.name ?? 'No name'}
|
||||
date={company.createdAt ?? ''}
|
||||
renderTitleEditComponent={() => (
|
||||
<CompanyNameEditableField company={company} />
|
||||
)}
|
||||
/>
|
||||
<PropertyBox extraPadding={true}>
|
||||
<EditableFieldMutationContext.Provider
|
||||
value={useUpdateOneCompanyMutation}
|
||||
>
|
||||
<EditableFieldEntityIdContext.Provider value={company.id}>
|
||||
{companyShowFieldDefinition.map((fieldDefinition) => {
|
||||
return (
|
||||
<EditableFieldDefinitionContext.Provider
|
||||
value={fieldDefinition}
|
||||
key={fieldDefinition.id}
|
||||
>
|
||||
<GenericEditableField />
|
||||
</EditableFieldDefinitionContext.Provider>
|
||||
);
|
||||
})}
|
||||
</EditableFieldEntityIdContext.Provider>
|
||||
</EditableFieldMutationContext.Provider>
|
||||
</PropertyBox>
|
||||
<CompanyTeam company={company}></CompanyTeam>
|
||||
</ShowPageLeftContainer>
|
||||
<ShowPageRightContainer
|
||||
entity={{
|
||||
id: company.id,
|
||||
type: ActivityTargetableEntityType.Company,
|
||||
}}
|
||||
timeline
|
||||
tasks
|
||||
notes
|
||||
emails
|
||||
/>
|
||||
</ShowPageContainer>
|
||||
</RecoilScope>
|
||||
</WithTopBarContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -18,6 +18,7 @@ import { ShowPageLeftContainer } from '@/ui/layout/show-page/components/ShowPage
|
||||
import { ShowPageRightContainer } from '@/ui/layout/show-page/components/ShowPageRightContainer';
|
||||
import { ShowPageSummaryCard } from '@/ui/layout/show-page/components/ShowPageSummaryCard';
|
||||
import { ShowPageRecoilScopeContext } from '@/ui/layout/states/ShowPageRecoilScopeContext';
|
||||
import { PageTitle } from '@/ui/utilities/page-title/PageTitle';
|
||||
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
|
||||
import {
|
||||
useUpdateOnePersonMutation,
|
||||
@ -63,66 +64,73 @@ export function PersonShow() {
|
||||
}
|
||||
|
||||
return (
|
||||
<WithTopBarContainer
|
||||
title={person.firstName ?? ''}
|
||||
icon={<IconUser size={theme.icon.size.md} />}
|
||||
hasBackButton
|
||||
isFavorite={isFavorite}
|
||||
onFavoriteButtonClick={handleFavoriteButtonClick}
|
||||
extraButtons={[
|
||||
<ShowPageAddButton
|
||||
key="add"
|
||||
entity={{
|
||||
id: person.id,
|
||||
type: ActivityTargetableEntityType.Person,
|
||||
}}
|
||||
/>,
|
||||
]}
|
||||
>
|
||||
<RecoilScope SpecificContext={ShowPageRecoilScopeContext}>
|
||||
<ShowPageContainer>
|
||||
<ShowPageLeftContainer>
|
||||
<ShowPageSummaryCard
|
||||
id={person.id}
|
||||
title={person.displayName ?? 'No name'}
|
||||
logoOrAvatar={person.avatarUrl ?? undefined}
|
||||
date={person.createdAt ?? ''}
|
||||
renderTitleEditComponent={() =>
|
||||
person ? <PeopleFullNameEditableField people={person} /> : <></>
|
||||
}
|
||||
onUploadPicture={onUploadPicture}
|
||||
/>
|
||||
<PropertyBox extraPadding={true}>
|
||||
<EditableFieldMutationContext.Provider
|
||||
value={useUpdateOnePersonMutation}
|
||||
>
|
||||
<EditableFieldEntityIdContext.Provider value={person.id}>
|
||||
{personShowFieldDefinition.map((fieldDefinition) => {
|
||||
return (
|
||||
<EditableFieldDefinitionContext.Provider
|
||||
value={fieldDefinition}
|
||||
key={fieldDefinition.id}
|
||||
>
|
||||
<GenericEditableField />
|
||||
</EditableFieldDefinitionContext.Provider>
|
||||
);
|
||||
})}
|
||||
</EditableFieldEntityIdContext.Provider>
|
||||
</EditableFieldMutationContext.Provider>
|
||||
</PropertyBox>
|
||||
</ShowPageLeftContainer>
|
||||
<ShowPageRightContainer
|
||||
<>
|
||||
<PageTitle title={person.displayName || 'No Name'} />
|
||||
<WithTopBarContainer
|
||||
title={person.firstName ?? ''}
|
||||
icon={<IconUser size={theme.icon.size.md} />}
|
||||
hasBackButton
|
||||
isFavorite={isFavorite}
|
||||
onFavoriteButtonClick={handleFavoriteButtonClick}
|
||||
extraButtons={[
|
||||
<ShowPageAddButton
|
||||
key="add"
|
||||
entity={{
|
||||
id: person.id ?? '',
|
||||
id: person.id,
|
||||
type: ActivityTargetableEntityType.Person,
|
||||
}}
|
||||
timeline
|
||||
tasks
|
||||
notes
|
||||
emails
|
||||
/>
|
||||
</ShowPageContainer>
|
||||
</RecoilScope>
|
||||
</WithTopBarContainer>
|
||||
/>,
|
||||
]}
|
||||
>
|
||||
<RecoilScope SpecificContext={ShowPageRecoilScopeContext}>
|
||||
<ShowPageContainer>
|
||||
<ShowPageLeftContainer>
|
||||
<ShowPageSummaryCard
|
||||
id={person.id}
|
||||
title={person.displayName ?? 'No name'}
|
||||
logoOrAvatar={person.avatarUrl ?? undefined}
|
||||
date={person.createdAt ?? ''}
|
||||
renderTitleEditComponent={() =>
|
||||
person ? (
|
||||
<PeopleFullNameEditableField people={person} />
|
||||
) : (
|
||||
<></>
|
||||
)
|
||||
}
|
||||
onUploadPicture={onUploadPicture}
|
||||
/>
|
||||
<PropertyBox extraPadding={true}>
|
||||
<EditableFieldMutationContext.Provider
|
||||
value={useUpdateOnePersonMutation}
|
||||
>
|
||||
<EditableFieldEntityIdContext.Provider value={person.id}>
|
||||
{personShowFieldDefinition.map((fieldDefinition) => {
|
||||
return (
|
||||
<EditableFieldDefinitionContext.Provider
|
||||
value={fieldDefinition}
|
||||
key={fieldDefinition.id}
|
||||
>
|
||||
<GenericEditableField />
|
||||
</EditableFieldDefinitionContext.Provider>
|
||||
);
|
||||
})}
|
||||
</EditableFieldEntityIdContext.Provider>
|
||||
</EditableFieldMutationContext.Provider>
|
||||
</PropertyBox>
|
||||
</ShowPageLeftContainer>
|
||||
<ShowPageRightContainer
|
||||
entity={{
|
||||
id: person.id ?? '',
|
||||
type: ActivityTargetableEntityType.Person,
|
||||
}}
|
||||
timeline
|
||||
tasks
|
||||
notes
|
||||
emails
|
||||
/>
|
||||
</ShowPageContainer>
|
||||
</RecoilScope>
|
||||
</WithTopBarContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { HelmetProvider } from 'react-helmet-async';
|
||||
import { MemoryRouter, Route, Routes } from 'react-router-dom';
|
||||
import { Decorator } from '@storybook/react';
|
||||
|
||||
@ -29,11 +30,13 @@ export const PageDecorator: Decorator<{
|
||||
initialEntries={[computeLocation(args.routePath, args.routeParams)]}
|
||||
>
|
||||
<FullHeightStorybookLayout>
|
||||
<DefaultLayout>
|
||||
<Routes>
|
||||
<Route path={args.routePath} element={<Story />} />
|
||||
</Routes>
|
||||
</DefaultLayout>
|
||||
<HelmetProvider>
|
||||
<DefaultLayout>
|
||||
<Routes>
|
||||
<Route path={args.routePath} element={<Story />} />
|
||||
</Routes>
|
||||
</DefaultLayout>
|
||||
</HelmetProvider>
|
||||
</FullHeightStorybookLayout>
|
||||
</MemoryRouter>
|
||||
</ClientConfigProvider>
|
||||
|
||||
38
front/src/utils/title-utils.ts
Normal file
38
front/src/utils/title-utils.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { AppBasePath } from '@/types/AppBasePath';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
|
||||
export function getPageTitleFromPath(pathname: string): string {
|
||||
switch (pathname) {
|
||||
case AppPath.Verify:
|
||||
return 'Verify';
|
||||
case AppPath.SignIn:
|
||||
return 'Sign In';
|
||||
case AppPath.SignUp:
|
||||
return 'Sign Up';
|
||||
case AppPath.Invite:
|
||||
return 'Invite';
|
||||
case AppPath.CreateWorkspace:
|
||||
return 'Create Workspace';
|
||||
case AppPath.CreateProfile:
|
||||
return 'Create Profile';
|
||||
case AppPath.PeoplePage:
|
||||
return 'People';
|
||||
case AppPath.CompaniesPage:
|
||||
return 'Companies';
|
||||
case AppPath.TasksPage:
|
||||
return 'Tasks';
|
||||
case AppPath.OpportunitiesPage:
|
||||
return 'Opportunities';
|
||||
case `${AppBasePath.Settings}/${SettingsPath.ProfilePage}`:
|
||||
return 'Profile';
|
||||
case `${AppBasePath.Settings}/${SettingsPath.Experience}`:
|
||||
return 'Experience';
|
||||
case `${AppBasePath.Settings}/${SettingsPath.WorkspaceMembersPage}`:
|
||||
return 'Workspace Members';
|
||||
case `${AppBasePath.Settings}/${SettingsPath.Workspace}`:
|
||||
return 'Workspace';
|
||||
default:
|
||||
return 'Twenty';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user