Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add error and catch boundaries #554

Merged
merged 6 commits into from
Apr 11, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
33 changes: 33 additions & 0 deletions app/components/errors/seen-error-layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Box, Button, Heading, Text } from '@chakra-ui/react';
import type { ThrownResponse } from '@remix-run/react';
import { useNavigate } from '@remix-run/react';
import { getErrorMessageFromStatusCode } from '~/utils';

interface SeenErrorLayoutProps {
result: ThrownResponse<number, any>;
mapStatusToErrorText?: (statusCode: number) => string;
}

export default function SeenErrorLayout({
result,
mapStatusToErrorText = getErrorMessageFromStatusCode,
}: SeenErrorLayoutProps) {
const navigate = useNavigate();

return (
<Box textAlign="center" py={10} px={6}>
<Heading as="h2" size="2xl">
{result.status}
</Heading>
<Text fontSize="18px" mt={3} mb={2}>
{mapStatusToErrorText(result.status)}
</Text>
<Text color={'gray.500'} mb={6}>
{result.data}
</Text>
<Button colorScheme="brand" color="white" variant="solid" onClick={() => navigate('/')}>
Go to Home
</Button>
</Box>
);
}
24 changes: 24 additions & 0 deletions app/components/errors/unseen-error-layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Box, Button, Heading, Text } from '@chakra-ui/react';
import { useNavigate } from '@remix-run/react';

interface UnseenErrorLayoutProps {
errorText: string;
}

export default function UnseenErrorLayout({ errorText }: UnseenErrorLayoutProps) {
const navigate = useNavigate();

return (
<Box textAlign="center" py="10" px="">
<Heading as="h2" size="2xl">
Ooops, unexpected error
</Heading>
<Text fontSize="18px" mt="3" mb="8">
{errorText}
</Text>
<Button colorScheme="brand" color="white" variant="solid" onClick={() => navigate('/')}>
Go to Home
</Button>
</Box>
);
}
14 changes: 13 additions & 1 deletion app/routes/__index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Box, Container } from '@chakra-ui/react';
import { Outlet } from '@remix-run/react';
import { Outlet, useCatch } from '@remix-run/react';
import SeenErrorLayout from '~/components/errors/seen-error-layout';
import UnseenErrorLayout from '~/components/errors/unseen-error-layout';
import Header from '~/components/header';

export default function Index() {
Expand All @@ -14,3 +16,13 @@ export default function Index() {
</Box>
);
}

export function ErrorBoundary({ error }: { error: Error }) {
return <UnseenErrorLayout errorText={`Unexpected error: ${error.message}`} />;
}

export function CatchBoundary() {
const caught = useCatch();

return <SeenErrorLayout result={caught} />;
}
29 changes: 27 additions & 2 deletions app/routes/__index/certificate/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Flex, Heading } from '@chakra-ui/react';
import type { LoaderArgs, ActionArgs } from '@remix-run/node';
import { json } from '@remix-run/node';
import { typedjson, useTypedLoaderData } from 'remix-typedjson';
import { useRevalidator } from '@remix-run/react';
import { useCatch, useRevalidator } from '@remix-run/react';
import { useInterval } from 'react-use';
import { useMemo } from 'react';
import dayjs from 'dayjs';
Expand All @@ -12,9 +12,11 @@ import pendingSvg from '~/assets/undraw_processing_re_tbdu.svg';
import Loading from '~/components/display-page';
import CertificateAvailable from '~/components/certificate/certificate-available';
import CertificateRequestView from '~/components/certificate/certificate-request';
import { useEffectiveUser } from '~/utils';
import { getErrorMessageFromStatusCode, useEffectiveUser } from '~/utils';
import { getCertificateByUsername } from '~/models/certificate.server';
import { addCertRequest } from '~/queues/certificate/certificate-flow.server';
import UnseenErrorLayout from '~/components/errors/unseen-error-layout';
import SeenErrorLayout from '~/components/errors/seen-error-layout';

export const loader = async ({ request }: LoaderArgs) => {
const username = await requireUsername(request);
Expand Down Expand Up @@ -75,6 +77,29 @@ function formatDate(val: Date): string {
return date;
}

function mapStatusToErrorText(statusCode: number): string {
switch (statusCode) {
case 404:
return 'Sorry we could not find you certificate';
Myrfion marked this conversation as resolved.
Show resolved Hide resolved
case 409:
return 'Sorry, your certificate is not issued yet. Please try again later.';
default:
return getErrorMessageFromStatusCode(statusCode);
}
}

export function CatchBoundary() {
const caught = useCatch();

return <SeenErrorLayout result={caught} mapStatusToErrorText={mapStatusToErrorText} />;
}

export function ErrorBoundary() {
return (
<UnseenErrorLayout errorText="We got an unexpected error working with your certificate, but don't worry our team is already on it's way to fix it" />
);
}

export default function CertificateIndexRoute() {
const user = useEffectiveUser();
const revalidator = useRevalidator();
Expand Down
36 changes: 32 additions & 4 deletions app/routes/__index/dns-records/$dnsRecordId.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,18 @@ import DnsRecordForm from '~/components/dns-record/form';
import { requireUser } from '~/session.server';
import { getDnsRecordById, updateDnsRecordById } from '~/models/dns-record.server';
import { isNameValid, UpdateDnsRecordSchema } from '~/lib/dns.server';
import { useActionData } from '@remix-run/react';
import { buildDomain } from '~/utils';
import { useActionData, useCatch, useParams } from '@remix-run/react';
import { buildDomain, getErrorMessageFromStatusCode } from '~/utils';
import SeenErrorLayout from '~/components/errors/seen-error-layout';
import UnseenErrorLayout from '~/components/errors/unseen-error-layout';

export const loader = async ({ request, params }: LoaderArgs) => {
await requireUser(request);

const { dnsRecordId } = params;
if (!dnsRecordId) {
throw new Response('dnsRecordId should be string', {

if (!dnsRecordId || !parseInt(dnsRecordId)) {
throw new Response('DNS Record ID is not valid', {
status: 400,
});
}
Expand Down Expand Up @@ -59,6 +62,31 @@ export const action = async ({ request }: ActionArgs) => {
return redirect(`/dns-records`);
};

function mapStatusToErrorText(statusCode: number): string {
switch (statusCode) {
case 400:
return 'Provided record id is not valid';
default:
return getErrorMessageFromStatusCode(statusCode);
}
}

export function CatchBoundary() {
const caught = useCatch();

return <SeenErrorLayout result={caught} mapStatusToErrorText={mapStatusToErrorText} />;
}

export function ErrorBoundary() {
const { dnsRecordId } = useParams();

return (
<UnseenErrorLayout
errorText={`We got an unexpected error working with your dns record with id ${dnsRecordId}, but don't worry our team is already on it's way to fix it`}
/>
);
}

export default function DnsRecordRoute() {
const dnsRecord = useTypedLoaderData<typeof loader>();
const actionData = useActionData();
Expand Down
28 changes: 27 additions & 1 deletion app/routes/__index/dns-records/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AddIcon } from '@chakra-ui/icons';
import { Button, Center, Flex, Heading, Stat, StatLabel, StatNumber, Text } from '@chakra-ui/react';
import { Link } from '@remix-run/react';
import { Link, useCatch } from '@remix-run/react';
import { typedjson, useTypedLoaderData } from 'remix-typedjson';
import { json } from '@remix-run/node';
import { z } from 'zod';
Expand All @@ -17,6 +17,9 @@ import { requireUsername } from '~/session.server';
import logger from '~/lib/logger.server';

import type { LoaderArgs, ActionArgs } from '@remix-run/node';
import SeenErrorLayout from '~/components/errors/seen-error-layout';
import UnseenErrorLayout from '~/components/errors/unseen-error-layout';
import { getErrorMessageFromStatusCode } from '~/utils';

export type DnsRecordActionIntent = 'renew-dns-record' | 'delete-dns-record';

Expand Down Expand Up @@ -77,6 +80,29 @@ export const action = async ({ request }: ActionArgs) => {
}
};

function mapStatusToErrorText(statusCode: number): string {
switch (statusCode) {
case 404:
return 'Sorry we could not find your DNS Record';
case 400:
return 'We got an error processing requested action on your dns record';
Myrfion marked this conversation as resolved.
Show resolved Hide resolved
default:
return getErrorMessageFromStatusCode(statusCode);
}
}

export function CatchBoundary() {
const caught = useCatch();

return <SeenErrorLayout result={caught} mapStatusToErrorText={mapStatusToErrorText} />;
}

export function ErrorBoundary() {
return (
<UnseenErrorLayout errorText="We got an unexpected error working with your DNS Records, but don't worry our team is already on it's way to fix it" />
);
}

export default function DnsRecordsIndexRoute() {
const data = useTypedLoaderData<typeof loader>();

Expand Down
21 changes: 21 additions & 0 deletions app/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,24 @@ export async function getChildrenValuesOfQueueName<CT>({

return filteredChildrenValues;
}

export function getErrorMessageFromStatusCode(statusCode: number): string {
switch (statusCode) {
case 400:
return 'Bad Request: The server cannot process the request because it is malformed or invalid.';
case 401:
return 'Unauthorized: The request requires authentication, and the user is not authenticated.';
case 403:
return 'Forbidden: The server understands the request but refuses to authorize it.';
case 404:
return 'Not Found: The server cannot find the requested resource.';
case 500:
return 'Internal Server Error: The server encountered an unexpected condition that prevented it from fulfilling the request.';
case 502:
return 'Bad Gateway: The server received an invalid response from the upstream server.';
case 503:
return 'Service Unavailable: The server is currently unable to handle the request due to a temporary overload or maintenance.';
default:
return 'An error has occurred.';
}
}