diff options
| author | Armand Philippot <git@armandphilippot.com> | 2023-12-12 18:50:03 +0100 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2023-12-12 18:50:03 +0100 |
| commit | 85c4c42bd601270d7be0f34a0767a34bb85e29bb (patch) | |
| tree | 16a07a89cf209139672592fd6988f0c028acb7e9 /src/utils/hooks/use-breadcrumbs/use-breadcrumbs.ts | |
| parent | 93f87c10783e3d76f1dec667779aedffcae33a39 (diff) | |
refactor(hooks): rewrite useBreadcrumbs hook
* use next/router to get the slug instead of using props
* handle cases where the current page title is not provided
* update JSON-LD schema to match the example in documentation
* add tests
Diffstat (limited to 'src/utils/hooks/use-breadcrumbs/use-breadcrumbs.ts')
| -rw-r--r-- | src/utils/hooks/use-breadcrumbs/use-breadcrumbs.ts | 144 |
1 files changed, 144 insertions, 0 deletions
diff --git a/src/utils/hooks/use-breadcrumbs/use-breadcrumbs.ts b/src/utils/hooks/use-breadcrumbs/use-breadcrumbs.ts new file mode 100644 index 0000000..a0132c0 --- /dev/null +++ b/src/utils/hooks/use-breadcrumbs/use-breadcrumbs.ts @@ -0,0 +1,144 @@ +import { useRouter } from 'next/router'; +import { useCallback } from 'react'; +import { useIntl } from 'react-intl'; +import type { BreadcrumbList } from 'schema-dts'; +import type { BreadcrumbsItem } from '../../../components'; +import { PAGINATED_ROUTE_PREFIX, ROUTES } from '../../constants'; +import { capitalize } from '../../helpers'; + +const is404 = (slug: string) => slug === ROUTES.NOT_FOUND; +const isArticle = (slug: string) => slug === ROUTES.ARTICLE; +const isBlog = (slug: string) => slug === ROUTES.BLOG; +const isHome = (slug: string) => slug === ROUTES.HOME; +const isPaginated = (slug: string) => + new RegExp(`${PAGINATED_ROUTE_PREFIX}/[0-9]+$`).test(slug); +const isProjects = (slug: string) => slug === ROUTES.PROJECTS; +const isSearch = (slug: string) => slug.startsWith(ROUTES.SEARCH); +const isThematic = (slug: string) => slug === ROUTES.THEMATICS; +const isTopic = (slug: string) => slug === ROUTES.TOPICS; + +const getCrumbsSlug = ( + acc: string[], + current: string, + index: number +): string[] => [ + ...acc, + ...(isSearch(`/${current}`) ? [`/${current.split('?s=')[0]}`] : []), + `${acc[acc.length - 1]}${index === 0 ? '' : '/'}${current}`, +]; + +export type UseBreadcrumbsReturn = { + items: BreadcrumbsItem[]; + schema: BreadcrumbList; +}; + +export const useBreadcrumbs = ( + currentPageTitle?: string +): UseBreadcrumbsReturn => { + const { asPath } = useRouter(); + const intl = useIntl(); + + const getCrumbLabel = useCallback( + (slug: string) => { + switch (true) { + case is404(slug): + return intl.formatMessage({ + defaultMessage: '404: Not found', + description: 'UseBreadcrumbs: page not found label', + id: 'EH+dam', + }); + case isBlog(slug): + return intl.formatMessage({ + defaultMessage: 'Blog', + description: 'UseBreadcrumbs: blog label', + id: 'K6aSZi', + }); + case isHome(slug): + return intl.formatMessage({ + defaultMessage: 'Home', + description: 'UseBreadcrumbs: home label', + id: 'aZIuPO', + }); + case isPaginated(slug): + return intl.formatMessage( + { + defaultMessage: 'Page {number}', + description: 'UseBreadcrumbs: paginated route label', + id: '/5tytV', + }, + { number: slug.split('/').pop() } + ); + case isProjects(slug): + return intl.formatMessage({ + defaultMessage: 'Projects', + description: 'UseBreadcrumbs: projects label', + id: 'rkz8C6', + }); + case isSearch(slug): + if (slug.includes('?s=')) + return intl.formatMessage( + { + defaultMessage: 'Search results for "{query}"', + description: 'UseBreadcrumbs: search results label', + id: 'gSevGm', + }, + { query: slug.split('?s=').pop() } + ); + + return intl.formatMessage({ + defaultMessage: 'Search', + description: 'UseBreadcrumbs: search label', + id: 'iHC3Qx', + }); + default: + return capitalize( + (slug.split('/').pop() ?? slug).replaceAll('-', ' ') + ); + } + }, + [intl] + ); + + const items = asPath + .split('/') + .filter((part) => part) + .reduce(getCrumbsSlug, [ROUTES.HOME as string]) + .filter((slug) => !slug.endsWith(PAGINATED_ROUTE_PREFIX)) + .map((slug, index, arr) => { + if (isArticle(slug) || isThematic(slug) || isTopic(slug)) + return { + id: ROUTES.BLOG, + label: getCrumbLabel(ROUTES.BLOG), + slug: ROUTES.BLOG, + }; + + const isLastSlug = index === arr.length - 1; + + return { + id: slug, + label: + isLastSlug && currentPageTitle + ? currentPageTitle + : getCrumbLabel(slug), + slug, + }; + }); + + return { + items, + schema: { + '@type': 'BreadcrumbList', + '@id': 'breadcrumbs', + itemListElement: items.map((item, index) => { + return { + '@type': 'ListItem', + item: { + '@id': item.slug, + name: item.label, + }, + position: index + 1, + }; + }), + }, + }; +}; |
