Skip to content

Commit

Permalink
Issue 98: add ErrorPage component (#3900)
Browse files Browse the repository at this point in the history
* feat: add ErrorPage and BaseErrorPage components

* add story for each error page

* extract translations

* add changeset

* refactor into one ErrorPage component with message map

* rename hook and variable

* linting

* update ErrorStatuses type so we get intellisense for handled codes

* add sticker sheet

* run prettier

* restrict type to known status codes

* use components BrandMoment

* fix text body styles

* fix prettier icons

---------

Co-authored-by: Geoffrey Chong <gyfchong@users.noreply.github.com>
Co-authored-by: Geoffrey Chong <gyf.chong@gmail.com>
  • Loading branch information
3 people committed Jul 25, 2023
1 parent b5eece4 commit 5c3fa76
Show file tree
Hide file tree
Showing 14 changed files with 521 additions and 6 deletions.
5 changes: 5 additions & 0 deletions .changeset/calm-rockets-doubt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@kaizen/components": minor
---

feat: add ErrorPage component
89 changes: 86 additions & 3 deletions packages/components/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,98 @@
"variants_enabled": true
},
"filterDateRangePicker.dateFrom": {
"description": "Label for the 'Date from' date range input field",
"description": "Label for the 'Date from' field",
"message": "Date from"
},
"filterDateRangePicker.dateTo": {
"description": "Label for the 'Date to' date range input field",
"description": "Label for the 'date to' field",
"message": "Date to"
},
"filterDateRangePicker.inputFormat": {
"description": "Label for the 'Input format' subtext",
"description": "Label for the 'Input format' field",
"message": "Input format"
},
"kzErrorPage": {
"description": "Label for contact button",
"message": "Contact support"
},
"kzErrorPage.400.message": {
"description": "Call to action instructions for the user",
"message": "Sorry but your request couldn’t be completed. Go back and try again, or head to Home"
},
"kzErrorPage.400.title": {
"description": "Heading for page",
"message": "Your request has slipped into the void"
},
"kzErrorPage.401.message": {
"description": "Call to action instructions for the user",
"message": "Sorry but we can't verify if you're able to view this page. Go back and try again, or head to Home"
},
"kzErrorPage.401.title": {
"description": "Main title for page",
"message": "You can't view this page"
},
"kzErrorPage.403.message": {
"description": "Call to action instructions for the user",
"message": "Sorry but it looks like you don’t have permission to view this page. Go back and try again, or head to Home"
},
"kzErrorPage.403.title": {
"description": "Main title for page",
"message": "You can't view this page"
},
"kzErrorPage.404.message": {
"description": "Call to action instructions for the user",
"message": "Sorry but we can't fing the page you're looking for. Go back and try again, or head to Home"
},
"kzErrorPage.404.title": {
"description": "Main title of page",
"message": "Missing pages are one of life's mysteries"
},
"kzErrorPage.422.message": {
"description": "Call to action instructions for the user",
"message": "Sorry but your change couldn't be made. Go back and try again, or head to Home"
},
"kzErrorPage.422.title": {
"description": "Main title of page",
"message": "Change never comes easy"
},
"kzErrorPage.500": {
"description": "Call to action instructions for the user",
"message": "Sorry there's an issue with our system and this page can't be displayed. Go back and try again, or head to Home"
},
"kzErrorPage.500.title": {
"description": "Main title of page",
"message": "Something's gone wrong on our side"
},
"kzErrorPage.502.message": {
"description": "Call to action instructions for the user",
"message": "Sorry about this. The best thing to do is go back and try again."
},
"kzErrorPage.502.title": {
"description": "Main title of page",
"message": "You can't view this page"
},
"kzErrorPage.503.message": {
"description": "Call to action instructions for the user",
"message": "Sorry about this. The best thing to do is go back and try again."
},
"kzErrorPage.503.title": {
"description": "Main title of page",
"message": "You can't view this page"
},
"kzErrorPage.504.message": {
"description": "Call to action instructions for the user",
"message": "Sorry about this. The best thing to do is go back and try again."
},
"kzErrorPage.504.title": {
"description": "Main title of page",
"message": "You can't view this page"
},
"kzErrorPage.errorCode": {
"message": "Error code {code}"
},
"kzErrorPage.goToHome": {
"description": "Home button label",
"message": "Go to Home"
}
}
5 changes: 4 additions & 1 deletion packages/components/src/BrandMoment/BrandMoment.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ $breakpoint-xl: 1366px; // TODO: create an xl breakpoint in design-tokens and re
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: $spacing-lg;

&.informative {
background: $color-blue-100;
Expand Down Expand Up @@ -121,6 +120,10 @@ $breakpoint-xl: 1366px; // TODO: create an xl breakpoint in design-tokens and re
margin-bottom: $spacing-lg;
}

.textBody {
margin-bottom: $spacing-lg;
}

.secondaryAction {
margin-block-start: $spacing-sm;
}
Expand Down
4 changes: 2 additions & 2 deletions packages/components/src/BrandMoment/BrandMoment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,12 @@ export const BrandMoment = ({
{text.body && (
<Paragraph
variant="intro-lede"
classNameOverride={styles.body}
classNameOverride={styles.textBody}
>
{text.body}
</Paragraph>
)}
{body && <div className={styles.body}>{body}</div>}
{body && <div className={styles.textBody}>{body}</div>}
<div className={styles.actions}>
{primaryAction && (
<Button
Expand Down
5 changes: 5 additions & 0 deletions packages/components/src/ErrorPage/ErrorPage.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@import "~@kaizen/design-tokens/sass/spacing";

.paragraphPadding {
padding: $spacing-24 0;
}
98 changes: 98 additions & 0 deletions packages/components/src/ErrorPage/ErrorPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import React, { HTMLAttributes } from "react"
import { FormattedMessage, useIntl } from "@cultureamp/i18n-react-intl"
import classNames from "classnames"
import { BrandMomentError } from "@kaizen/draft-illustration"
import { Paragraph } from "@kaizen/typography"
import { BrandMoment } from "~components/BrandMoment"
import { ArrowRightIcon } from "~components/SVG/icons/ArrowRightIcon"
import { EmailIcon } from "~components/SVG/icons/EmailIcon"
import { OverrideClassName } from "~types/OverrideClassName"
import { ErrorStatuses, useErrorMessages } from "./hooks"
import styles from "./ErrorPage.module.scss"

const getMailToHref = (code: ErrorStatuses): string => {
const supportEmail = "support@cultureamp.com"
const subject = "Houston we have a problem"
const body = `Hi there,\n\nI received a ${code} error page while I was trying to...`
return encodeURI(`mailto:${supportEmail}?subject=${subject}&amp;body=${body}`)
}

const HOME_HREF = "/app/home"

export type ErrorPageProps = {
code: ErrorStatuses
title?: string
message?: React.ReactNode | string
callToAction?: {
onContactSupport: () => void
homeHref?: string
}
} & OverrideClassName<HTMLAttributes<HTMLDivElement>>

export function ErrorPage({
code,
title,
message,
callToAction,
classNameOverride,
}: ErrorPageProps): JSX.Element {
const { formatMessage } = useIntl()
const content = useErrorMessages(code)

const actions = {
primary: { href: callToAction?.homeHref || HOME_HREF },
secondary: callToAction?.onContactSupport
? { onClick: callToAction.onContactSupport }
: { href: getMailToHref(code) },
}

return (
<div className={classNames(classNameOverride)}>
<BrandMoment
header={<></>}
body={
<>
<div className={styles.paragraphPadding}>
<Paragraph variant="intro-lede">
{message || content.message}
</Paragraph>
</div>
<Paragraph color="dark-reduced-opacity" variant="small">
<FormattedMessage
id="kzErrorPage.errorCode"
defaultMessage="Error code {code}"
values={{ code }}
/>
</Paragraph>
</>
}
illustration={<BrandMomentError isAnimated loop />}
mood="negative"
primaryAction={{
...actions.primary,
icon: <ArrowRightIcon role="presentation" />,
iconPosition: "end",
label: formatMessage({
id: "kzErrorPage.goToHome",
defaultMessage: "Go to Home",
description: "Home button label",
}),
}}
secondaryAction={{
...actions.secondary,
icon: <EmailIcon role="presentation" />,
label: formatMessage({
id: "kzErrorPage",
defaultMessage: "Contact support",
description: "Label for contact button",
}),
}}
text={{
title: title || content.title,
}}
/>
</div>
)
}

ErrorPage.displayName = "ErrorPage"
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import React from "react"
import { Meta, StoryFn } from "@storybook/react"
import { StickerSheet } from "../../../../../storybook/components/StickerSheet"
import { ErrorPage } from "../ErrorPage"

export default {
title: "Pages/Error Page",
parameters: {
chromatic: { disable: false },
controls: { disable: true },
},
} satisfies Meta<typeof ErrorPage>

const StickerSheetTemplate: StoryFn<{ isReversed: boolean }> = ({
isReversed,
}) => (
<>
<StickerSheet heading="Error 400" isReversed={isReversed}>
<StickerSheet.Body>
<StickerSheet.Row>
<ErrorPage code="400" />
</StickerSheet.Row>
</StickerSheet.Body>
</StickerSheet>
<StickerSheet heading="Error 401" isReversed={isReversed}>
<StickerSheet.Body>
<StickerSheet.Row>
<ErrorPage code="401" />
</StickerSheet.Row>
</StickerSheet.Body>
</StickerSheet>
<StickerSheet heading="Error 403" isReversed={isReversed}>
<StickerSheet.Body>
<StickerSheet.Row>
<ErrorPage code="403" />
</StickerSheet.Row>
</StickerSheet.Body>
</StickerSheet>
<StickerSheet heading="Error 404" isReversed={isReversed}>
<StickerSheet.Body>
<StickerSheet.Row>
<ErrorPage code="404" />
</StickerSheet.Row>
</StickerSheet.Body>
</StickerSheet>
<StickerSheet heading="Error 422" isReversed={isReversed}>
<StickerSheet.Body>
<StickerSheet.Row>
<ErrorPage code="422" />
</StickerSheet.Row>
</StickerSheet.Body>
</StickerSheet>
<StickerSheet heading="Error 500" isReversed={isReversed}>
<StickerSheet.Body>
<StickerSheet.Row>
<ErrorPage code="500" />
</StickerSheet.Row>
</StickerSheet.Body>
</StickerSheet>
<StickerSheet heading="Error 502" isReversed={isReversed}>
<StickerSheet.Body>
<StickerSheet.Row>
<ErrorPage code="502" />
</StickerSheet.Row>
</StickerSheet.Body>
</StickerSheet>
<StickerSheet heading="Error 503" isReversed={isReversed}>
<StickerSheet.Body>
<StickerSheet.Row>
<ErrorPage code="503" />
</StickerSheet.Row>
</StickerSheet.Body>
</StickerSheet>
<StickerSheet heading="Error 504" isReversed={isReversed}>
<StickerSheet.Body>
<StickerSheet.Row>
<ErrorPage code="504" />
</StickerSheet.Row>
</StickerSheet.Body>
</StickerSheet>
<StickerSheet heading="Custom error" isReversed={isReversed}>
<StickerSheet.Body>
<StickerSheet.Row>
<ErrorPage
code="400"
title="This is a 400 custom title"
message="This is a custom 400 message"
callToAction={{
onContactSupport: () => alert("Custom handler"),
homeHref: "/anewhome",
}}
/>
</StickerSheet.Row>
</StickerSheet.Body>
</StickerSheet>
</>
)

export const StickerSheetDefault = StickerSheetTemplate.bind({})
41 changes: 41 additions & 0 deletions packages/components/src/ErrorPage/_docs/ErrorPage.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Meta, StoryObj } from "@storybook/react"
import { ComponentDocsTemplate } from "../../../../../storybook/components/DocsContainer"
import { statusCodes } from "../hooks"
import { ErrorPage } from "../index"

const meta = {
tags: ["autodocs"],
title: "Pages/Error Page",
component: ErrorPage,
parameters: {
docs: {
container: ComponentDocsTemplate,
canvas: {
sourceState: "shown",
},
},
installation: [
"npm install @kaizen/components",
"import { ErrorPage } from `@kaizen/components`",
],
resourceLinks: {
/** @todo: Add Github link (adjust as needed) */
sourceCode:
"https://github.com/cultureamp/kaizen-design-system/tree/master/packages/components/src/ErrorPage",
},
},
} satisfies Meta<typeof ErrorPage>

export default meta

export const Playground: StoryObj<typeof meta> = {
args: {
code: "400",
},
argTypes: {
code: {
options: statusCodes,
control: { type: "select" },
},
},
}
1 change: 1 addition & 0 deletions packages/components/src/ErrorPage/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./useErrorMessages"
Loading

0 comments on commit 5c3fa76

Please sign in to comment.