diff --git a/next.config.mjs b/next.config.mjs index ff6f94a..adbc227 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -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'], diff --git a/src/components/error/ErrorInfo.tsx b/src/components/error/ErrorInfo.tsx index e42b55e..07cca5f 100644 --- a/src/components/error/ErrorInfo.tsx +++ b/src/components/error/ErrorInfo.tsx @@ -11,7 +11,8 @@ 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; @@ -19,12 +20,12 @@ type Props = { }; }; -const ErrorInfo = ({ message, statusCode, misc }: Props) => { +const ErrorInfo = ({ message, statusCode, misc, originalPath }: Props) => { const title = statusCode ? `${message} (${statusCode})` : message; return ( <> - + { the homepage + , or view this route{' '} + + on IMDb + .

)} diff --git a/src/interfaces/shared/error.ts b/src/interfaces/shared/error.ts index 477d852..9beabf8 100644 --- a/src/interfaces/shared/error.ts +++ b/src/interfaces/shared/error.ts @@ -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, 'name'>; diff --git a/src/layouts/Header.tsx b/src/layouts/Header.tsx index b95c9e5..5154742 100644 --- a/src/layouts/Header.tsx +++ b/src/layouts/Header.tsx @@ -1,21 +1,14 @@ -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 ( -
- + @@ -23,7 +16,7 @@ const Header = (props: Props) => { libremdb - {props.full && ( + {full && ( )}
- - - View on IMDb (opens in new tab) - + + View on IMDb (opens in new tab) @@ -68,7 +55,7 @@ const Header = (props: Props) => {
- {props.full && ( + {full && (

A free & open source IMDb front-end @@ -83,10 +70,7 @@ const Header = (props: Props) => { nitter , and{' '} - + many others . diff --git a/src/layouts/Layout.tsx b/src/layouts/Layout.tsx index 756693f..d26e019 100644 --- a/src/layouts/Layout.tsx +++ b/src/layouts/Layout.tsx @@ -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 ( <> -
+
{children}
diff --git a/src/pages/[...error]/index.tsx b/src/pages/[...error]/index.tsx new file mode 100644 index 0000000..a732dd5 --- /dev/null +++ b/src/pages/[...error]/index.tsx @@ -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; + +const Error404 = ({ originalPath }: Props) => { + return ; +}; + +export default Error404; + +type Data = { originalPath: string }; +type Params = { error: string[] }; + +export const getServerSideProps: GetServerSideProps = async ctx => { + ctx.res.statusCode = error.statusCode; + ctx.res.statusMessage = error.message; + + return { props: { originalPath: ctx.resolvedUrl } }; +}; diff --git a/src/pages/find/index.tsx b/src/pages/find/index.tsx index 7bf325a..cb63df5 100644 --- a/src/pages/find/index.tsx +++ b/src/pages/find/index.tsx @@ -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 ; +const BasicSearch = ({ data: { title, results }, error, originalPath }: Props) => { + if (error) return ; + + let layoutClassName = styles.find; + if (!title) layoutClassName += ' ' + styles.find__home; return ( <> - + {title && ( // only showing when user has searched for something )} @@ -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 = 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); @@ -57,7 +64,7 @@ export const getServerSideProps: GetServerSideProps = 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; @@ -68,6 +75,7 @@ export const getServerSideProps: GetServerSideProps = asy props: { error: { message, statusCode }, data: { title: query, results: null }, + originalPath, }, }; } diff --git a/src/pages/name/[nameId]/index.tsx b/src/pages/name/[nameId]/index.tsx index 51c35d3..e3afcd5 100644 --- a/src/pages/name/[nameId]/index.tsx +++ b/src/pages/name/[nameId]/index.tsx @@ -14,8 +14,8 @@ import styles from 'src/styles/modules/pages/name/name.module.scss'; type Props = InferGetServerSidePropsType; -const NameInfo = ({ data, error }: Props) => { - if (error) return ; +const NameInfo = ({ data, error, originalPath }: Props) => { + if (error) return ; return ( <> @@ -24,7 +24,7 @@ const NameInfo = ({ data, error }: Props) => { description={data.basic.bio.short + '...'} imgUrl={data.basic.poster?.url && getProxiedIMDbImgUrl(data.basic.poster.url)} /> - +
@@ -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 = 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 } }; } }; diff --git a/src/pages/title/[titleId]/index.tsx b/src/pages/title/[titleId]/index.tsx index fb27c5e..3fab840 100644 --- a/src/pages/title/[titleId]/index.tsx +++ b/src/pages/title/[titleId]/index.tsx @@ -15,8 +15,8 @@ import styles from 'src/styles/modules/pages/title/title.module.scss'; type Props = InferGetServerSidePropsType; // TO-DO: make a wrapper page component to display errors, if present in props -const TitleInfo = ({ data, error }: Props) => { - if (error) return ; +const TitleInfo = ({ data, error, originalPath }: Props) => { + if (error) return ; const info = { meta: data.meta, @@ -34,7 +34,7 @@ const TitleInfo = ({ data, error }: Props) => { description={data.basic.plot ?? undefined} imgUrl={data.basic.poster?.url && getProxiedIMDbImgUrl(data.basic.poster.url)} /> - + @@ -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 = 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 } }; } };