Skip to content

Commit

Permalink
feat: add playful 404 and error pages (#3248)
Browse files Browse the repository at this point in the history
fix #3235
  • Loading branch information
andre-code authored Oct 15, 2024
1 parent 28fbf0f commit 0eebdcb
Show file tree
Hide file tree
Showing 18 changed files with 418 additions and 198 deletions.
13 changes: 0 additions & 13 deletions client/src/error-boundary/ErrorBoundary.module.scss

This file was deleted.

79 changes: 49 additions & 30 deletions client/src/error-boundary/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@
import * as Sentry from "@sentry/react";
import cx from "classnames";
import { ReactNode, useCallback } from "react";

import styles from "./ErrorBoundary.module.scss";
import v2Styles from "../styles/renku_bootstrap.scss?inline";
import { Helmet } from "react-helmet";
import { ArrowLeft } from "react-bootstrap-icons";
import { StyleHandler } from "../index";
import rkOopsImg from "../styles/assets/oops.svg";
import rkOopsV2Img from "../styles/assets/oopsV2.svg";
import useLegacySelector from "../utils/customHooks/useLegacySelector.hook";

interface AppErrorBoundaryProps {
children?: ReactNode;
Expand All @@ -44,41 +45,59 @@ export function AppErrorBoundary({ children }: AppErrorBoundaryProps) {
}, []);

return (
<Sentry.ErrorBoundary onError={onError} fallback={ErrorPage}>
<Sentry.ErrorBoundary onError={onError} fallback={<ErrorPage />}>
{children}
</Sentry.ErrorBoundary>
);
}

function ErrorPage() {
const isV2 = location.pathname.startsWith("/v2");
const logged = useLegacySelector((state) => state.stateModel.user.logged);
return (
<>
<Helmet>
<style type="text/css">{v2Styles}</style>
</Helmet>
<div className={styles.error}>
<div className={cx("container-xxl", "p-5")}>
<div className={cx("p-4", "bg-white", "bg-opacity-75")}>
<h1>Application Error</h1>
<h3 className="mb-4">
Ooops! It looks like we are having some issues!
</h3>
<StyleHandler />
<div
className={cx("d-flex", "flex-column", "align-items-center", "mt-5")}
>
<div className={cx("p-4")}>
<img src={isV2 ? rkOopsV2Img : rkOopsImg} />
<h3
className={cx(
isV2 ? "text-primary" : "text-rk-green",
"fw-bold",
"mt-3"
)}
>
It looks like we are having some issues.
</h3>

<p className="mb-0">
You can try to{" "}
<a
className={cx("btn", "btn-primary", "mx-1")}
href={window.location.href}
onClick={() => window.location.reload()}
>
reload the page
</a>{" "}
or go to the{" "}
<a className={cx("btn", "btn-primary", "mx-1")} href="/">
Renku home page
</a>
</p>
</div>
<p className="mb-0">
You can try to{" "}
<a
className={cx(
"btn",
isV2 ? "btn-outline-primary" : "btn-outline-rk-green",
"m-2"
)}
href={window.location.href}
onClick={() => window.location.reload()}
>
Reload the page
</a>{" "}
or{" "}
<a
className={cx(
"btn",
isV2 ? "btn-primary" : "btn-rk-green",
"m-2"
)}
href="/"
>
<ArrowLeft className={cx("me-2", "text-icon")} />
{logged ? "Return to the dashboard" : "Return to home page"}
</a>
</p>
</div>
</div>
</>
Expand Down
10 changes: 9 additions & 1 deletion client/src/features/projectsV2/new/GroupNew.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import useLegacySelector from "../../../utils/customHooks/useLegacySelector.hook
import { slugFromTitle } from "../../../utils/helpers/HelperFunctions";

import { RtkOrNotebooksError } from "../../../components/errors/RtkErrorAlert";
import LoginAlert from "../../../components/loginAlert/LoginAlert";
import type { GroupPostRequest } from "../api/namespace.api";
import { usePostGroupsMutation } from "../api/projectV2.enhanced-api";
import DescriptionFormField from "../fields/DescriptionFormField";
Expand Down Expand Up @@ -164,9 +165,16 @@ function GroupMetadataForm() {
export default function GroupNew() {
const user = useLegacySelector((state) => state.stateModel.user);
if (!user.logged) {
const textIntro = "Only authenticated users can create new groups.";
const textPost = "to create a new group.";
return (
<ContainerWrap>
<h2>Please log in to create a group.</h2>
<h2 className={cx("mb-0", "me-2")}>New group</h2>
<LoginAlert
logged={user.logged}
textIntro={textIntro}
textPost={textPost}
/>
</ContainerWrap>
);
}
Expand Down
15 changes: 14 additions & 1 deletion client/src/features/projectsV2/new/ProjectV2New.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
* limitations under the License.
*/

import cx from "classnames";
import { FormEvent, useCallback, useEffect } from "react";
import { useDispatch } from "react-redux";
import { generatePath, useNavigate } from "react-router-dom-v5-compat";
Expand All @@ -30,6 +31,7 @@ import useLegacySelector from "../../../utils/customHooks/useLegacySelector.hook
import type { ProjectPost } from "../api/projectV2.api";
import { usePostProjectsMutation } from "../api/projectV2.enhanced-api";

import LoginAlert from "../../../components/loginAlert/LoginAlert";
import WipBadge from "../shared/WipBadge";
import { ProjectV2DescriptionAndRepositories } from "../show/ProjectV2DescriptionAndRepositories";
import ProjectFormSubmitGroup from "./ProjectV2FormSubmitGroup";
Expand Down Expand Up @@ -168,7 +170,18 @@ export default function ProjectV2New() {
}, [dispatch]);
const { currentStep } = useAppSelector((state) => state.newProjectV2);
if (!user.logged) {
return <h2>Please log in to create a project.</h2>;
const textIntro = "Only authenticated users can create new projects.";
const textPost = "to create a new project.";
return (
<div className={cx("d-flex", "flex-column")}>
<h2 className={cx("mb-0", "me-2")}>New project</h2>
<LoginAlert
logged={user.logged}
textIntro={textIntro}
textPost={textPost}
/>
</div>
);
}
return (
<FormSchema
Expand Down
58 changes: 24 additions & 34 deletions client/src/features/projectsV2/notFound/GroupNotFound.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,12 @@
import { SerializedError } from "@reduxjs/toolkit";
import { FetchBaseQueryError } from "@reduxjs/toolkit/query";
import cx from "classnames";
import { useCallback, useState } from "react";
import { ArrowLeft } from "react-bootstrap-icons";
import { Link, useParams } from "react-router-dom-v5-compat";
import { Button, Col, Collapse, Row } from "reactstrap";

import ContainerWrap from "../../../components/container/ContainerWrap";
import { RtkOrNotebooksError } from "../../../components/errors/RtkErrorAlert";
import { ABSOLUTE_ROUTES } from "../../../routing/routes.constants";
import rkNotFoundImgV2 from "../../../styles/assets/not-foundV2.svg";

interface GroupNotFoundProps {
error?: FetchBaseQueryError | SerializedError | undefined | null;
Expand All @@ -35,11 +33,6 @@ interface GroupNotFoundProps {
export default function GroupNotFound({ error }: GroupNotFoundProps) {
const { slug: groupSlug } = useParams<{ slug: string }>();

const [detailsOpen, setDetailsOpen] = useState(false);
const onClickDetails = useCallback(() => {
setDetailsOpen((open) => !open);
}, []);

const notFoundText = groupSlug ? (
<>
We could not find the group{" "}
Expand All @@ -51,38 +44,35 @@ export default function GroupNotFound({ error }: GroupNotFoundProps) {

return (
<ContainerWrap>
<Row>
<Col>
<h1>Error 404</h1>
<h2 className="mb-3">Group not found</h2>

<p>{notFoundText}</p>
<p>It is possible that the group has been deleted by its owner.</p>

<div>
<div className="d-flex">
<div className={cx("m-auto", "d-flex", "flex-column")}>
<h3
className={cx(
"text-primary",
"fw-bold",
"my-0",
"d-flex",
"align-items-center",
"gap-3"
)}
>
<img src={rkNotFoundImgV2} />
Group not found
</h3>
<div className={cx("text-start", "mt-3")}>
<p>{notFoundText}</p>
<p>It is possible that the group has been deleted by its owner.</p>
{error && <RtkOrNotebooksError error={error} dismissible={false} />}
<Link
to={ABSOLUTE_ROUTES.v2.root}
className={cx("btn", "btn-outline-primary")}
className={cx("btn", "btn-primary")}
>
<ArrowLeft className={cx("bi", "me-1")} />
Return to the dashboard
Return to the groups list
</Link>
</div>

{error && (
<>
<div className="my-3">
<Button color="outline-secondary" onClick={onClickDetails}>
Show error details
</Button>
</div>
<Collapse isOpen={detailsOpen}>
<RtkOrNotebooksError error={error} dismissible={false} />
</Collapse>
</>
)}
</Col>
</Row>
</div>
</div>
</ContainerWrap>
);
}
63 changes: 27 additions & 36 deletions client/src/features/projectsV2/notFound/ProjectNotFound.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,13 @@
import { SerializedError } from "@reduxjs/toolkit";
import { FetchBaseQueryError } from "@reduxjs/toolkit/query";
import cx from "classnames";
import { useCallback, useState } from "react";
import { ArrowLeft } from "react-bootstrap-icons";
import { Link, useParams } from "react-router-dom-v5-compat";
import { Button, Col, Collapse, Row } from "reactstrap";

import ContainerWrap from "../../../components/container/ContainerWrap";
import { RtkOrNotebooksError } from "../../../components/errors/RtkErrorAlert";
import { ABSOLUTE_ROUTES } from "../../../routing/routes.constants";
import rkNotFoundImgV2 from "../../../styles/assets/not-foundV2.svg";

interface ProjectNotFoundProps {
error?: FetchBaseQueryError | SerializedError | undefined | null;
Expand All @@ -43,11 +42,6 @@ export default function ProjectNotFound({ error }: ProjectNotFoundProps) {
slug: string;
}>();

const [detailsOpen, setDetailsOpen] = useState(false);
const onClickDetails = useCallback(() => {
setDetailsOpen((open) => !open);
}, []);

const notFoundText =
namespace && slug ? (
<>
Expand All @@ -69,41 +63,38 @@ export default function ProjectNotFound({ error }: ProjectNotFoundProps) {

return (
<ContainerWrap>
<Row>
<Col>
<h1>Error 404</h1>
<h2 className="mb-3">Project not found</h2>

<p>{notFoundText}</p>
<p>
It is possible that the project has been deleted by its owner or you
do not have permission to access it.
</p>

<div>
<div className={cx("d-flex")}>
<div className={cx("m-auto", "d-flex", "flex-column")}>
<h3
className={cx(
"text-primary",
"fw-bold",
"my-0",
"d-flex",
"align-items-center",
"gap-3"
)}
>
<img src={rkNotFoundImgV2} />
Project not found
</h3>
<div className={cx("text-start", "mt-3")}>
<p>{notFoundText}</p>
<p>
It is possible that the project has been deleted by its owner or
you do not have permission to access it.
</p>
{error && <RtkOrNotebooksError error={error} dismissible={false} />}
<Link
to={ABSOLUTE_ROUTES.v2.root}
className={cx("btn", "btn-outline-primary")}
className={cx("btn", "btn-primary")}
>
<ArrowLeft className={cx("bi", "me-1")} />
Return to the dashboard
Return to the projects list
</Link>
</div>

{error && (
<>
<div className="my-3">
<Button color="outline-secondary" onClick={onClickDetails}>
Show error details
</Button>
</div>
<Collapse isOpen={detailsOpen}>
<RtkOrNotebooksError error={error} dismissible={false} />
</Collapse>
</>
)}
</Col>
</Row>
</div>
</div>
</ContainerWrap>
);
}
Loading

0 comments on commit 0eebdcb

Please sign in to comment.