Skip to content

Commit

Permalink
fix(error): fix incorrect 'view on IMDb' link on error page
Browse files Browse the repository at this point in the history
the error was due to a faulty logic. 'useRouter' was being used to detect pathname, which doesn't
keep original url on 404 page.
this commit fixes that.
this commit also makes it easy to go to
IMDb by adding a clear link on error page.

closes #50
  • Loading branch information
zyachel committed Jun 3, 2023
1 parent 23eeae3 commit 0aea2f4
Show file tree
Hide file tree
Showing 9 changed files with 94 additions and 69 deletions.
23 changes: 8 additions & 15 deletions next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,14 @@
const nextConfig = {
reactStrictMode: true,
swcMinify: true,
async rewrites() {
return {
afterFiles: [
{
source: '/',
destination: '/find',
},
],
fallback: [
{
source: '/:path*',
destination: '/404',
},
],
};
async redirects() {
return [
{
source: '/',
destination: '/find',
permanent: true,
},
];
},
images: {
domains: ['m.media-amazon.com'],
Expand Down
16 changes: 13 additions & 3 deletions src/components/error/ErrorInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,21 @@ import styles from 'src/styles/modules/components/error/error-info.module.scss';
type Props = {
message: string;
statusCode?: number;
// props specific to error boundary.
originalPath?: string;
/** props specific to error boundary. */
misc?: {
subtext: string;
buttonText: string;
buttonClickHandler: () => void;
};
};

const ErrorInfo = ({ message, statusCode, misc }: Props) => {
const ErrorInfo = ({ message, statusCode, misc, originalPath }: Props) => {
const title = statusCode ? `${message} (${statusCode})` : message;
return (
<>
<Meta title={title} description='you encountered an error page!' />
<Layout className={styles.error}>
<Layout className={styles.error} originalPath={originalPath}>
<svg
className={styles.gnu}
focusable='false'
Expand Down Expand Up @@ -52,6 +53,15 @@ const ErrorInfo = ({ message, statusCode, misc }: Props) => {
<Link href='/'>
<a className='link'>the homepage</a>
</Link>
, or view this route{' '}
<a
className='link'
href={`https://www.imdb.com${originalPath ?? ''}`}
target='_blank'
rel='noreferrer'
>
on IMDb
</a>
.
</p>
)}
Expand Down
8 changes: 3 additions & 5 deletions src/interfaces/shared/error.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
export type AppError = {
message: string;
statusCode: number;
stack?: any;
};
import { AppError as AppErrorClass } from 'src/utils/helpers';

export type AppError = Omit<InstanceType<typeof AppErrorClass>, 'name'>;
34 changes: 9 additions & 25 deletions src/layouts/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,22 @@
import { ReactNode } from 'react';
import { useRouter } from 'next/router';
import Link from 'next/link';
import ThemeToggler from 'src/components/buttons/ThemeToggler';
import styles from 'src/styles/modules/layout/header.module.scss';

type Props = { full?: boolean; children?: ReactNode };

const Header = (props: Props) => {
const { asPath: path } = useRouter();
type Props = { full?: boolean; originalPath?: string };

const Header = ({ full, originalPath }: Props) => {
return (
<header
id='header'
className={`${styles.header} ${props.full ? styles.header__about : ''}`}
>
<header id='header' className={`${styles.header} ${full ? styles.header__about : ''}`}>
<div className={styles.topbar}>
<Link href='/'>
<Link href='/find'>
<a aria-label='go to homepage' className={styles.logo}>
<svg className={styles.logo__icon} role='img' aria-hidden>
<use href='/svg/sprite.svg#icon-logo'></use>
</svg>
<span className={styles.logo__text}>libremdb</span>
</a>
</Link>
{props.full && (
{full && (
<nav className={styles.nav}>
<ul className={styles.nav__list}>
<li className={styles.nav__item}>
Expand All @@ -45,14 +38,8 @@ const Header = (props: Props) => {
</nav>
)}
<div className={styles.misc}>
<a
href={`https://www.imdb.com${path}`}
target='_blank'
rel='noreferrer'
>
<span className='visually-hidden'>
View on IMDb (opens in new tab)
</span>
<a href={`https://www.imdb.com${originalPath ?? ''}`} target='_blank' rel='noreferrer'>
<span className='visually-hidden'>View on IMDb (opens in new tab)</span>
<svg className='icon' role='img' aria-hidden>
<use href='/svg/sprite.svg#icon-external-link'></use>
</svg>
Expand All @@ -68,7 +55,7 @@ const Header = (props: Props) => {
<ThemeToggler className={styles.themeToggler} />
</div>
</div>
{props.full && (
{full && (
<div className={styles.hero}>
<h1 className={`heading heading__primary ${styles.hero__text}`}>
A free & open source IMDb front-end
Expand All @@ -83,10 +70,7 @@ const Header = (props: Props) => {
nitter
</a>
, and{' '}
<a
href='https://github.com/digitalblossom/alternative-frontends'
className='link'
>
<a href='https://github.com/digitalblossom/alternative-frontends' className='link'>
many others
</a>
.
Expand Down
5 changes: 3 additions & 2 deletions src/layouts/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ type Props = {
full?: true;
children: ReactNode;
className: string;
originalPath?: string;
};

const Layout = ({ full, children, className }: Props) => {
const Layout = ({ full, children, className, originalPath }: Props) => {
return (
<>
<Header full={full} />
<Header full={full} originalPath={originalPath} />
<main id='main' className={`main ${className}`}>
{children}
</main>
Expand Down
25 changes: 25 additions & 0 deletions src/pages/[...error]/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { GetServerSideProps, InferGetServerSidePropsType } from 'next';
import ErrorInfo from 'src/components/error/ErrorInfo';

const error = {
statusCode: 404,
message: 'Not found, sorry.',
} as const;

type Props = InferGetServerSidePropsType<typeof getServerSideProps>;

const Error404 = ({ originalPath }: Props) => {
return <ErrorInfo {...error} originalPath={originalPath} />;
};

export default Error404;

type Data = { originalPath: string };
type Params = { error: string[] };

export const getServerSideProps: GetServerSideProps<Data, Params> = async ctx => {
ctx.res.statusCode = error.statusCode;
ctx.res.statusMessage = error.message;

return { props: { originalPath: ctx.resolvedUrl } };
};
22 changes: 15 additions & 7 deletions src/pages/find/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,16 @@ const getMetadata = (title: string | null) => ({
: 'Search for anything on libremdb, a free & open source IMDb front-end',
});

const BasicSearch = ({ data: { title, results }, error }: Props) => {
if (error) return <ErrorInfo message={error.message} statusCode={error.statusCode} />;
const BasicSearch = ({ data: { title, results }, error, originalPath }: Props) => {
if (error) return <ErrorInfo {...error} originalPath={originalPath} />;

let layoutClassName = styles.find;
if (!title) layoutClassName += ' ' + styles.find__home;

return (
<>
<Meta {...getMetadata(title)} />
<Layout className={`${styles.find} ${!title && styles.find__home}`}>
<Layout className={layoutClassName} originalPath={originalPath}>
{title && ( // only showing when user has searched for something
<Results results={results} title={title} className={styles.results} />
)}
Expand All @@ -38,17 +41,21 @@ const BasicSearch = ({ data: { title, results }, error }: Props) => {
};

// TODO: use generics for passing in queryParams(to components) for better type-checking.
type Data =
type Data = (
| { data: { title: string; results: Find }; error: null }
| { data: { title: null; results: null }; error: null }
| { data: { title: string; results: null }; error: AppError };
| { data: { title: string; results: null }; error: AppError }
) & {
originalPath: string;
};

export const getServerSideProps: GetServerSideProps<Data, FindQueryParams> = async ctx => {
// sample query str: find/?q=babylon&s=tt&ttype=ft&exact=true
const queryObj = ctx.query as FindQueryParams;
const query = queryObj.q?.trim();
const originalPath = ctx.resolvedUrl;

if (!query) return { props: { data: { title: null, results: null }, error: null } };
if (!query) return { props: { data: { title: null, results: null }, error: null, originalPath } };

try {
const entries = Object.entries(queryObj);
Expand All @@ -57,7 +64,7 @@ export const getServerSideProps: GetServerSideProps<Data, FindQueryParams> = asy
const res = await getOrSetApiCache(findKey(queryStr), basicSearch, queryStr);

return {
props: { data: { title: query, results: res }, error: null },
props: { data: { title: query, results: res }, error: null, originalPath },
};
} catch (error: any) {
const { message, statusCode } = error;
Expand All @@ -68,6 +75,7 @@ export const getServerSideProps: GetServerSideProps<Data, FindQueryParams> = asy
props: {
error: { message, statusCode },
data: { title: query, results: null },
originalPath,
},
};
}
Expand Down
15 changes: 9 additions & 6 deletions src/pages/name/[nameId]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import styles from 'src/styles/modules/pages/name/name.module.scss';

type Props = InferGetServerSidePropsType<typeof getServerSideProps>;

const NameInfo = ({ data, error }: Props) => {
if (error) return <ErrorInfo message={error.message} statusCode={error.statusCode} />;
const NameInfo = ({ data, error, originalPath }: Props) => {
if (error) return <ErrorInfo {...error} originalPath={originalPath} />;

return (
<>
Expand All @@ -24,7 +24,7 @@ const NameInfo = ({ data, error }: Props) => {
description={data.basic.bio.short + '...'}
imgUrl={data.basic.poster?.url && getProxiedIMDbImgUrl(data.basic.poster.url)}
/>
<Layout className={styles.name}>
<Layout className={styles.name} originalPath={originalPath}>
<Basic data={data.basic} className={styles.basic} />
<Media className={styles.media} media={data.media} />
<div className={styles.textarea}>
Expand All @@ -41,23 +41,26 @@ const NameInfo = ({ data, error }: Props) => {
);
};

type Data = { data: Name; error: null } | { error: AppError; data: null };
type Data = ({ data: Name; error: null } | { error: AppError; data: null }) & {
originalPath: string;
};
type Params = { nameId: string };

export const getServerSideProps: GetServerSideProps<Data, Params> = async ctx => {
const nameId = ctx.params!.nameId;
const originalPath = ctx.resolvedUrl;

try {
const data = await getOrSetApiCache(nameKey(nameId), name, nameId);

return { props: { data, error: null } };
return { props: { data, error: null, originalPath } };
} catch (error: any) {
const { message, statusCode } = error;

ctx.res.statusCode = statusCode;
ctx.res.statusMessage = message;

return { props: { error: { message, statusCode }, data: null } };
return { props: { error: { message, statusCode }, data: null, originalPath } };
}
};

Expand Down
15 changes: 9 additions & 6 deletions src/pages/title/[titleId]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import styles from 'src/styles/modules/pages/title/title.module.scss';
type Props = InferGetServerSidePropsType<typeof getServerSideProps>;

// TO-DO: make a wrapper page component to display errors, if present in props
const TitleInfo = ({ data, error }: Props) => {
if (error) return <ErrorInfo message={error.message} statusCode={error.statusCode} />;
const TitleInfo = ({ data, error, originalPath }: Props) => {
if (error) return <ErrorInfo {...error} originalPath={originalPath} />;

const info = {
meta: data.meta,
Expand All @@ -34,7 +34,7 @@ const TitleInfo = ({ data, error }: Props) => {
description={data.basic.plot ?? undefined}
imgUrl={data.basic.poster?.url && getProxiedIMDbImgUrl(data.basic.poster.url)}
/>
<Layout className={styles.title}>
<Layout className={styles.title} originalPath={originalPath}>
<Basic data={data.basic} className={styles.basic} />
<Media className={styles.media} media={data.media} />
<Cast className={styles.cast} cast={data.cast} />
Expand All @@ -50,22 +50,25 @@ const TitleInfo = ({ data, error }: Props) => {
};

// TO-DO: make a getServerSideProps wrapper for handling errors
type Data = { data: Title; error: null } | { error: AppError; data: null };
type Data = ({ data: Title; error: null } | { error: AppError; data: null }) & {
originalPath: string;
};
type Params = { titleId: string };

export const getServerSideProps: GetServerSideProps<Data, Params> = async ctx => {
const titleId = ctx.params!.titleId;
const originalPath = ctx.resolvedUrl;

try {
const data = await getOrSetApiCache(titleKey(titleId), title, titleId);

return { props: { data, error: null } };
return { props: { data, error: null, originalPath } };
} catch (error: any) {
const { message, statusCode } = error;
ctx.res.statusCode = statusCode;
ctx.res.statusMessage = message;

return { props: { error: { message, statusCode }, data: null } };
return { props: { error: { message, statusCode }, data: null, originalPath } };
}
};

Expand Down

0 comments on commit 0aea2f4

Please sign in to comment.