Skip to content
This repository has been archived by the owner on Nov 4, 2024. It is now read-only.

Commit

Permalink
feature/COR-1516-404-page-improvements-multiple-pages (#4736)
Browse files Browse the repository at this point in the history
* feat(404-pages): Remove hardcoded article redirects

* feat(404-pages): Enable configuration of 404 pages in Sanity.

* feat(404-pages): Adjust schemas.

* feat(404-pages): Wrap breadcrumbs with a guard.

* feat(404-pages): Middleware redirect logic with parameters for each 404 page.

* feat(404-pages): Add new icons. Adjust KpiIconInput component so that it closes upon selecting an icon.

* feat(404-pages): Extract query to its own file and adjust not found schema.

* feat(404-pages): Update SVG fill

* feat(404-pages): Adjust query and schema. Delete old 404 page

* feat(404-pages): WIP new 404 pages.

* feat(404-pages): Finalize 404 pages.

* feat(404-pages): Fine tune implementation.

* feat(404-pages): PR Feedback, first round.

* feat(404-pages): PR Feedback, second round.

* feat(404-pages): Non middleware solution.

* feat(404-pages): Refactor.

* feat(404-pages): PR feedback and a few tweaks.

* feat(404 pages): Refactor not found page for general, landelijk, and articles.

* feat(404 pages): Change fallback back to blocking so that EN locale pages are built at runtime.

* feat(404 pages): Create dedicated GM 404 page with new rewrites.

* feat(404 pages): Formatting.

* feat(404 pages): Finalize rewrites for all Gemeente scenarios.

* feat(404 pages): Refactor and update utils typing.

* feat(404-pages): PR feedback - round 1

* feat(404-pages): PR feedback - round 2.

---------

Co-authored-by: VWSCoronaDashboard28 <[email protected]>
  • Loading branch information
APW26 and VWSCoronaDashboard28 authored Apr 17, 2023
1 parent e66cc13 commit 8552c9d
Show file tree
Hide file tree
Showing 10 changed files with 349 additions and 231 deletions.
31 changes: 31 additions & 0 deletions packages/app/src/components/not-found-pages/logic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { getClient } from '~/lib/sanity';
import { getNotFoundPageQuery } from '~/queries/get-not-found-page-query';
import { NotFoundPageConfiguration } from './types';

const determinePageTypeFromUrl = (url: string) => {
const levelsToPageTypeMapping: { [key: string]: string } = { landelijk: 'nl', gemeente: 'gm', artikelen: 'article', general: 'general' };
const pageType = levelsToPageTypeMapping[Object.keys(levelsToPageTypeMapping).find((key) => url.includes(`/${key}`)) ?? 'general'];

return pageType;
};

/**
* @function getNotFoundPageData
* @description Fetches the data for a given page type from Sanity and adds additional properties to the page configuration.
* @param url - The current request URL.
* @param locale - The selected locale.
* @returns {Promise<NotFoundPageConfiguration> | null} - Returns the page configuration if found, otherwise null.
*/
export const getNotFoundPageData = async (url: string, locale: string) => {
const pageType = url ? determinePageTypeFromUrl(url) : 'general';
const query = getNotFoundPageQuery(locale, pageType);
const client = await getClient();
const notFoundPageConfiguration: NotFoundPageConfiguration = await client.fetch(query);

if (!notFoundPageConfiguration) return null;

notFoundPageConfiguration.isGmPage = pageType === 'gm';
notFoundPageConfiguration.isGeneralPage = pageType === 'general';

return notFoundPageConfiguration;
};
39 changes: 39 additions & 0 deletions packages/app/src/components/not-found-pages/not-found-link.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { colors } from '@corona-dashboard/common';
import { ChevronRight } from '@corona-dashboard/icons';
import styled from 'styled-components';
import { space } from '~/style/theme';
import { getFilenameToIconName } from '~/utils/get-filename-to-icon-name';
import { Box } from '../base/box';
import DynamicIcon, { IconName } from '../get-icon-by-name';
import { Anchor } from '../typography';
import { NotFoundLinkProps } from './types';

export const NotFoundLink = ({ link: { linkUrl, linkLabel, linkIcon }, hasChevron, isCTA, ...restProps }: NotFoundLinkProps) => {
const iconNameFromFileName = linkIcon ? (getFilenameToIconName(linkIcon) as IconName) : null;
const icon = iconNameFromFileName ? <DynamicIcon color={colors.blue8} name={iconNameFromFileName} height="30px" width="30px" /> : undefined;

return (
<Box {...restProps}>
{icon}

<StyledAnchor hasIcon={!!icon} href={linkUrl} isCTA={isCTA} underline={!isCTA}>
{linkLabel}
</StyledAnchor>

{hasChevron && <ChevronRight color={colors.blue8} height="10px" />}
</Box>
);
};

interface StyledAnchorProps {
hasIcon: boolean;
isCTA: boolean | undefined;
}

const StyledAnchor = styled(Anchor)<StyledAnchorProps>`
margin-inline: ${({ hasIcon, isCTA }) => (hasIcon || isCTA ? space[2] : !hasIcon ? `0 ${space[2]}` : 0)};
&:hover {
text-decoration: none;
}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Content } from '~/domain/layout/content';
import { Layout } from '~/domain/layout/layout';
import { useIntl } from '~/intl';
import { Heading, Text } from '../typography';

export const NotFoundFallback = ({ lastGenerated }: { lastGenerated: string }) => {
const { commonTexts } = useIntl();

return (
<Layout {...commonTexts.notfound_metadata} lastGenerated={lastGenerated}>
<Content>
<Heading level={1}>{commonTexts.notfound_titel.text}</Heading>
<Text>{commonTexts.notfound_beschrijving.text}</Text>
</Content>
</Layout>
);
};
108 changes: 108 additions & 0 deletions packages/app/src/components/not-found-pages/not-found-page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { colors } from '@corona-dashboard/common';
import styled from 'styled-components';
import { GmComboBox } from '~/domain/layout/components/gm-combo-box';
import { Layout } from '~/domain/layout/layout';
import { useIntl } from '~/intl';
import { getImageProps } from '~/lib/sanity';
import { mediaQueries, radii, sizes, space } from '~/style/theme';
import { Box } from '../base/box';
import { RichContent } from '../cms/rich-content';
import { SanityImage } from '../cms/sanity-image';
import { Heading } from '../typography';
import { NotFoundLink } from './not-found-link';
import { NotFoundProps } from './types';

export const NotFoundPage = ({ lastGenerated, notFoundPageConfiguration }: NotFoundProps) => {
const {
commonTexts: { notfound_metadata },
} = useIntl();
const { title, description, isGmPage = true, isGeneralPage = false, image, links = undefined, cta = undefined } = notFoundPageConfiguration;

return (
<Layout {...notfound_metadata} lastGenerated={lastGenerated}>
<NotFoundLayout>
<Box minWidth="50%" display="flex" flexDirection="column">
<Box spacing={4} marginBottom={space[4]} maxWidth="400px" order={1}>
<Heading level={1}>{title}</Heading>
<RichContent blocks={description} elementAlignment="start" />
</Box>

{isGmPage && (
// Compensating for padding on the combo-box element using negative margins.
<Box margin={`-${space[4]} -${space[3]} 0`} maxWidth="400px" order={2}>
<GmComboBox selectedGmCode="" shouldFocusInput={false} />
</Box>
)}

{links && (
<Box order={isGeneralPage ? 3 : 4}>
{links.map((link, index) => (
<NotFoundLink
alignItems="center"
display="flex"
hasChevron
key={link.id}
link={link}
marginBottom={isGeneralPage ? (index === links.length - 1 ? space[4] : space[2]) : undefined}
/>
))}
</Box>
)}

{cta && Object.values(cta).some((item) => item !== null) && (
<NotFoundCTA
alignItems="center"
border={`1px solid ${colors.blue8}`}
borderRadius={`${radii[1]}px`}
className="not-found-content-cta"
display="inline-flex"
isCTA
link={{ linkUrl: cta.ctaLink, linkLabel: cta.ctaLabel, linkIcon: cta.ctaIcon || '' }}
marginBottom={isGeneralPage ? undefined : space[4]}
maxWidth="fit-content"
order={isGeneralPage ? 4 : 3}
padding={`${space[1]} ${space[2]}`}
/>
)}
</Box>

<Box display="flex" justifyContent={{ _: 'center', sm: 'flex-start' }} maxHeight="520px">
<SanityImage {...getImageProps(image, {})} />
</Box>
</NotFoundLayout>
</Layout>
);
};

const NotFoundCTA = NotFoundLink; // Renaming for the sake of readability.

const NotFoundLayout = styled.div`
display: flex;
flex-direction: column;
gap: ${space[4]};
justify-content: space-between;
margin: ${space[5]} auto;
max-width: ${sizes.maxWidth}px;
padding: 0 ${space[3]};
@media ${mediaQueries.sm} {
flex-direction: row;
padding: 0 ${space[4]};
}
@media ${mediaQueries.md} {
align-items: flex-start;
}
.not-found-content-cta {
transition: all 0.2s ease-in-out;
svg rect {
fill: ${colors.transparent};
}
&:hover {
background-color: ${colors.gray1};
}
}
`;
43 changes: 43 additions & 0 deletions packages/app/src/components/not-found-pages/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { PortableTextEntry } from '@sanity/block-content-to-react';
import { ImageBlock } from '~/types/cms';

type Link = {
id?: string;
linkIcon?: string;
linkLabel: string;
linkUrl: string;
};

export type NotFoundPageConfiguration = {
description: PortableTextEntry[];
image: ImageBlock;
isGeneralPage: boolean;
isGmPage: boolean;
title: string;
cta?: {
ctaIcon?: string;
ctaLabel: string;
ctaLink: string;
};
links?: Link[];
};

export interface NotFoundProps {
lastGenerated: string;
notFoundPageConfiguration: NotFoundPageConfiguration;
}

export interface NotFoundLinkProps {
alignItems: string;
display: string;
link: Link;
border?: string;
borderRadius?: string;
className?: string;
hasChevron?: boolean;
isCTA?: boolean;
marginBottom?: string;
maxWidth?: string;
order?: number;
padding?: string;
}
38 changes: 38 additions & 0 deletions packages/app/src/next-config/rewrites.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,44 @@ async function rewrites() {
source: '/gemeente/(g|G)(m|M):nr(\\d{4})/:page*',
destination: '/gemeente/GM:nr/:page*',
},
/**
* The rewrite below will match everything after /gemeente/ except for g/m or G/M or gm/GM.
* When matched, the destination is rewritten to /gemeente/code/404 so that it lands in the
* [...404].tsx catch-all route within packages/app/src/pages/gemeente/[code]/ The same applies
* to the two other gemeente rewrites below. This is a workaround for making sure all gemeente routes
* go to the correct 404 page ([...404].tsx).
*
* The regex states that if /gemeente/ is not followed by gm/GM/gM/Gm, then it should go to the 404 page.
* For example:
* 1. /gemeente/somethingWrong
* 2. /gemeente/blahblah
* 3. /gemeente/gblah
*/
{
source: '/gemeente/((?!gm|GM|gM|Gm).*):slug*',
destination: '/gemeente/code/404',
},
/**
* The regex below states that if the URL contains GM after /gemeente/, it must be followed by 4 digits.
* This will catch:
* 1. /gemeente/GM123
* 2. /gemeente/GM
* 3. /gemeente/GM123/rioolwater
*/
{
source: '/gemeente/(g|G)(m|M)((?!\\d{4}).*):slug*',
destination: '/gemeente/code/404',
},
/**
* The regex below matches URLs which contain g|G m|M followed by more than 4 digits, optionally
* followed by a forward slash, optionally followed by a string. This will catch:
* 1. /gemeente/GM12345
* 2. /gemeente/GM123456/rioolwater
*/
{
source: '/gemeente/(g|G)(m|M)(\\d{5,})(\\/?)(\\S*):slug*',
destination: '/gemeente/code/404',
},
],
};
}
Expand Down
Loading

0 comments on commit 8552c9d

Please sign in to comment.