From 8bb4071f0961206c09ec107bbf1f22cc0570e9be Mon Sep 17 00:00:00 2001 From: Mustajab Ikram Date: Mon, 28 Aug 2023 22:19:04 +0530 Subject: [PATCH] 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 --- front/package.json | 2 + front/src/App.tsx | 8 +- front/src/__stories__/App.stories.tsx | 5 +- front/src/index.tsx | 31 +++-- .../ui/utilities/page-title/PageTitle.tsx | 13 ++ front/src/pages/companies/CompanyShow.tsx | 120 +++++++++-------- front/src/pages/people/PersonShow.tsx | 124 ++++++++++-------- .../src/testing/decorators/PageDecorator.tsx | 13 +- front/src/utils/title-utils.ts | 38 ++++++ front/yarn.lock | 25 +++- 10 files changed, 242 insertions(+), 137 deletions(-) create mode 100644 front/src/modules/ui/utilities/page-title/PageTitle.tsx create mode 100644 front/src/utils/title-utils.ts diff --git a/front/package.json b/front/package.json index 5a42d8e3b..0c1663c75 100644 --- a/front/package.json +++ b/front/package.json @@ -19,6 +19,7 @@ "@types/node": "^16.18.4", "@types/react": "^18.0.25", "@types/react-dom": "^18.0.9", + "@types/react-helmet-async": "^1.0.3", "@types/react-modal": "^3.16.0", "afterframe": "^1.0.2", "apollo-link-rest": "^0.9.0", @@ -41,6 +42,7 @@ "react-datepicker": "^4.11.0", "react-dom": "^18.2.0", "react-dropzone": "^14.2.3", + "react-helmet-async": "^1.3.0", "react-hook-form": "^7.45.1", "react-hotkeys-hook": "^4.4.0", "react-loading-skeleton": "^3.3.1", diff --git a/front/src/App.tsx b/front/src/App.tsx index fbf9030b7..7a0c8bccf 100644 --- a/front/src/App.tsx +++ b/front/src/App.tsx @@ -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 ( <> + diff --git a/front/src/__stories__/App.stories.tsx b/front/src/__stories__/App.stories.tsx index 51488fe0c..75624836a 100644 --- a/front/src/__stories__/App.stories.tsx +++ b/front/src/__stories__/App.stories.tsx @@ -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 = { - + + + diff --git a/front/src/index.tsx b/front/src/index.tsx index 72884e847..d8d247c5c 100644 --- a/front/src/index.tsx +++ b/front/src/index.tsx @@ -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( - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + , diff --git a/front/src/modules/ui/utilities/page-title/PageTitle.tsx b/front/src/modules/ui/utilities/page-title/PageTitle.tsx new file mode 100644 index 000000000..63680379d --- /dev/null +++ b/front/src/modules/ui/utilities/page-title/PageTitle.tsx @@ -0,0 +1,13 @@ +import { Helmet } from 'react-helmet-async'; + +type OwnProps = { + title: string; +}; + +export function PageTitle({ title }: OwnProps) { + return ( + + {title} + + ); +} diff --git a/front/src/pages/companies/CompanyShow.tsx b/front/src/pages/companies/CompanyShow.tsx index f6f096d8e..e0049f769 100644 --- a/front/src/pages/companies/CompanyShow.tsx +++ b/front/src/pages/companies/CompanyShow.tsx @@ -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 ( - } - onFavoriteButtonClick={handleFavoriteButtonClick} - extraButtons={[ - , - ]} - > - - - - ( - - )} - /> - - - - {companyShowFieldDefinition.map((fieldDefinition) => { - return ( - - - - ); - })} - - - - - - + + } + onFavoriteButtonClick={handleFavoriteButtonClick} + extraButtons={[ + - - - + />, + ]} + > + + + + ( + + )} + /> + + + + {companyShowFieldDefinition.map((fieldDefinition) => { + return ( + + + + ); + })} + + + + + + + + + + ); } diff --git a/front/src/pages/people/PersonShow.tsx b/front/src/pages/people/PersonShow.tsx index c1bb0e687..c5d1fd4e9 100644 --- a/front/src/pages/people/PersonShow.tsx +++ b/front/src/pages/people/PersonShow.tsx @@ -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 ( - } - hasBackButton - isFavorite={isFavorite} - onFavoriteButtonClick={handleFavoriteButtonClick} - extraButtons={[ - , - ]} - > - - - - - person ? : <> - } - onUploadPicture={onUploadPicture} - /> - - - - {personShowFieldDefinition.map((fieldDefinition) => { - return ( - - - - ); - })} - - - - - + + } + hasBackButton + isFavorite={isFavorite} + onFavoriteButtonClick={handleFavoriteButtonClick} + extraButtons={[ + - - - + />, + ]} + > + + + + + person ? ( + + ) : ( + <> + ) + } + onUploadPicture={onUploadPicture} + /> + + + + {personShowFieldDefinition.map((fieldDefinition) => { + return ( + + + + ); + })} + + + + + + + + + ); } diff --git a/front/src/testing/decorators/PageDecorator.tsx b/front/src/testing/decorators/PageDecorator.tsx index f055baa2e..faada17af 100644 --- a/front/src/testing/decorators/PageDecorator.tsx +++ b/front/src/testing/decorators/PageDecorator.tsx @@ -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)]} > - - - } /> - - + + + + } /> + + + diff --git a/front/src/utils/title-utils.ts b/front/src/utils/title-utils.ts new file mode 100644 index 000000000..1889a2623 --- /dev/null +++ b/front/src/utils/title-utils.ts @@ -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'; + } +} diff --git a/front/yarn.lock b/front/yarn.lock index 40ce0cf94..cdc60bb8c 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -5603,6 +5603,13 @@ dependencies: "@types/react" "*" +"@types/react-helmet-async@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@types/react-helmet-async/-/react-helmet-async-1.0.3.tgz#89d581d6cb129e5357d39d7d1b41313b20523989" + integrity sha512-DqbSuZPSHiH1l3XI/y8LbhrAGNh+Bpc9QY4MsYRM1yD4+qhax8bN4DInUMpv/tNyIdjsa+1V8XXmbRx8W5dB0w== + dependencies: + react-helmet-async "*" + "@types/react-modal@^3.16.0": version "3.16.0" resolved "https://registry.yarnpkg.com/@types/react-modal/-/react-modal-3.16.0.tgz#b8d6be10de894139a2ea9f4a2505b1b5d02023df" @@ -16074,11 +16081,22 @@ react-fast-compare@3.2.1: resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.1.tgz#53933d9e14f364281d6cba24bfed7a4afb808b5f" integrity sha512-xTYf9zFim2pEif/Fw16dBiXpe0hoy5PxcD8+OwBnTtNLfIm3g6WxhKNurY+6OmdH1u6Ta/W/Vl6vjbYP1MFnDg== -react-fast-compare@^3.0.1: +react-fast-compare@^3.0.1, react-fast-compare@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49" integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ== +react-helmet-async@*, react-helmet-async@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/react-helmet-async/-/react-helmet-async-1.3.0.tgz#7bd5bf8c5c69ea9f02f6083f14ce33ef545c222e" + integrity sha512-9jZ57/dAn9t3q6hneQS0wukqC2ENOBgMNVEhb/ZG9ZSxUetzVIw4iAmEU38IaVg3QGYauQPhSeUTuIUtFglWpg== + dependencies: + "@babel/runtime" "^7.12.5" + invariant "^2.2.4" + prop-types "^15.7.2" + react-fast-compare "^3.2.0" + shallowequal "^1.1.0" + react-hook-form@^7.45.1: version "7.45.4" resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.45.4.tgz#73d228b704026ae95d7e5f7b207a681b173ec62a" @@ -17160,6 +17178,11 @@ shallow-equal@^1.2.1: resolved "https://registry.yarnpkg.com/shallow-equal/-/shallow-equal-1.2.1.tgz#4c16abfa56043aa20d050324efa68940b0da79da" integrity sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA== +shallowequal@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" + integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== + shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"