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

feature/COR-1516-404-page-improvements-multiple-pages #4736

Merged
merged 28 commits into from
Apr 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
3032d38
feat(404-pages): Remove hardcoded article redirects
Mar 24, 2023
9fd3942
feat(404-pages): Enable configuration of 404 pages in Sanity.
Mar 24, 2023
54611a4
feat(404-pages): Adjust schemas.
Mar 27, 2023
72e0f77
feat(404-pages): Wrap breadcrumbs with a guard.
Mar 27, 2023
5d3d98f
feat(404-pages): Middleware redirect logic with parameters for each 4…
Mar 29, 2023
82960b2
feat(404-pages): Add new icons. Adjust KpiIconInput component so that…
Mar 29, 2023
855d09b
feat(404-pages): Extract query to its own file and adjust not found s…
Mar 29, 2023
902241b
feat(404-pages): Update SVG fill
Mar 29, 2023
2b07400
feat(404-pages): Adjust query and schema. Delete old 404 page
Mar 29, 2023
c11f1fb
feat(404-pages): WIP new 404 pages.
Mar 29, 2023
24db72c
feat(404-pages): Finalize 404 pages.
Mar 31, 2023
028de01
feat(404-pages): Fine tune implementation.
Mar 31, 2023
553db17
feat(404-pages): PR Feedback, first round.
Apr 3, 2023
bf7cebf
feat(404-pages): PR Feedback, second round.
Apr 3, 2023
a466562
feat(404-pages): Non middleware solution.
Apr 3, 2023
e6c78a4
feat(404-pages): Refactor.
Apr 4, 2023
811ce31
feat(404-pages): PR feedback and a few tweaks.
Apr 4, 2023
528788a
Merge branch 'develop' into feature/COR-1516-404-page-improvements-mu…
Apr 5, 2023
47bcb06
feat(404 pages): Refactor not found page for general, landelijk, and …
Apr 12, 2023
d63cd69
feat(404 pages): Change fallback back to blocking so that EN locale p…
Apr 12, 2023
7b1244c
feat(404 pages): Create dedicated GM 404 page with new rewrites.
Apr 12, 2023
cbde93e
feat(404 pages): Formatting.
Apr 13, 2023
4fdd334
feat(404 pages): Finalize rewrites for all Gemeente scenarios.
Apr 13, 2023
60f8c76
feat(404 pages): Fix merge conflict
Apr 13, 2023
5653d70
feat(404 pages): Refactor and update utils typing.
Apr 14, 2023
852945e
feat(404-pages): PR feedback - round 1
Apr 14, 2023
6f84a6d
feat(404-pages): PR feedback - round 2.
Apr 17, 2023
060de99
Merge branch 'develop' into feature/COR-1516-404-page-improvements-mu…
Apr 17, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) => {
VWSCoronaDashboard26 marked this conversation as resolved.
Show resolved Hide resolved
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;
VWSCoronaDashboard26 marked this conversation as resolved.
Show resolved Hide resolved
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 {
VWSCoronaDashboard26 marked this conversation as resolved.
Show resolved Hide resolved
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