feat: major rewrite
the application is now rewritten in next.js. this commit also adds the ability to see trailers, did you know, more like this, etc. on title page. BREAKING CHANGE: the whole application is rewritten from scratch.
This commit is contained in:
7
src/pages/404.tsx
Normal file
7
src/pages/404.tsx
Normal file
@ -0,0 +1,7 @@
|
||||
import ErrorInfo from '../components/Error/ErrorInfo';
|
||||
|
||||
const Error404 = () => {
|
||||
return <ErrorInfo />;
|
||||
};
|
||||
|
||||
export default Error404;
|
6
src/pages/500.tsx
Normal file
6
src/pages/500.tsx
Normal file
@ -0,0 +1,6 @@
|
||||
import ErrorInfo from '../components/Error/ErrorInfo';
|
||||
|
||||
const Error500 = () => {
|
||||
return <ErrorInfo message='server messed up, sorry.' statusCode={500} />;
|
||||
};
|
||||
export default Error500;
|
40
src/pages/_app.tsx
Normal file
40
src/pages/_app.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import type { AppProps } from 'next/app';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
import ProgressBar from '../components/loaders/ProgressBar';
|
||||
import ThemeProvider from '../context/theme-context';
|
||||
|
||||
import '../styles/main.scss';
|
||||
|
||||
const ModifiedApp = ({ Component, pageProps }: AppProps) => {
|
||||
// for showing progress bar
|
||||
// could've used nprogress package, but didn't feel like it
|
||||
const router = useRouter();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handleStart = useCallback(() => setIsLoading(true), []);
|
||||
const handleEnd = useCallback(() => setIsLoading(false), []);
|
||||
|
||||
useEffect(() => {
|
||||
router.events.on('routeChangeStart', handleStart);
|
||||
router.events.on('routeChangeComplete', handleEnd);
|
||||
router.events.on('routeChangeError', handleEnd);
|
||||
|
||||
return () => {
|
||||
router.events.off('routeChangeStart', handleStart);
|
||||
router.events.off('routeChangeComplete', handleEnd);
|
||||
router.events.off('routeChangeError', handleEnd);
|
||||
};
|
||||
}, [router, handleStart, handleEnd]);
|
||||
//
|
||||
|
||||
return (
|
||||
<ThemeProvider>
|
||||
{isLoading && <ProgressBar />}
|
||||
<Component {...pageProps} />
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModifiedApp;
|
38
src/pages/_document.tsx
Normal file
38
src/pages/_document.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
import Document, { Html, Head, Main, NextScript } from 'next/document';
|
||||
|
||||
// for preventing Flash of inAccurate coloR Theme(fart)
|
||||
// chris coyier came up with that acronym(https://css-tricks.com/flash-of-inaccurate-color-theme-fart/)
|
||||
const setInitialTheme = `
|
||||
document.documentElement.dataset.js = true;
|
||||
document.documentElement.dataset.theme = (() => {
|
||||
const userPrefersTheme = window.localStorage.getItem('theme') || null;
|
||||
const browserPrefersDarkTheme = window.matchMedia(
|
||||
'(prefers-color-scheme: dark)'
|
||||
).matches;
|
||||
if (userPrefersTheme) return userPrefersTheme;
|
||||
else if (browserPrefersDarkTheme) return 'dark';
|
||||
else return 'light';
|
||||
})();
|
||||
`;
|
||||
|
||||
const ModifiedDocument = class extends Document {
|
||||
static async getInitialProps(ctx: any) {
|
||||
const initialProps = await Document.getInitialProps(ctx);
|
||||
return { ...initialProps };
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Html lang='en'>
|
||||
<Head />
|
||||
<body>
|
||||
<script dangerouslySetInnerHTML={{ __html: setInitialTheme }} />
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
</Html>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default ModifiedDocument;
|
176
src/pages/about/index.tsx
Normal file
176
src/pages/about/index.tsx
Normal file
@ -0,0 +1,176 @@
|
||||
/* eslint-disable react/no-unescaped-entities */
|
||||
import Link from 'next/link';
|
||||
import Meta from '../../components/Meta/Meta';
|
||||
import Layout from '../../layouts/Layout';
|
||||
|
||||
import styles from '../../styles/modules/pages/about/about.module.scss';
|
||||
|
||||
const About = () => {
|
||||
return (
|
||||
<>
|
||||
<Meta
|
||||
title='About'
|
||||
description='libremdb is a free & open source IMDb front-end. It allows you to see information about movies, tv shows, video games without any ads or tracking.'
|
||||
/>
|
||||
<Layout full className={styles.about}>
|
||||
<section id='features' className={styles.features}>
|
||||
<h2
|
||||
className={`heading heading__secondary ${styles.features__heading}`}
|
||||
>
|
||||
Some features
|
||||
</h2>
|
||||
<ul className={styles.features__list}>
|
||||
<li className={styles.feature}>
|
||||
<svg
|
||||
aria-hidden='true'
|
||||
focusable='false'
|
||||
role='img'
|
||||
className={styles.feature__icon}
|
||||
>
|
||||
<use href='/svg/sprite.svg#icon-eye-slash'></use>
|
||||
</svg>
|
||||
<h3
|
||||
className={`heading heading__tertiary ${styles.feature__heading}`}
|
||||
>
|
||||
No ads or tracking
|
||||
</h3>
|
||||
<p className={styles.feature__text}>
|
||||
Browse any movie info without being tracked or bombarded by
|
||||
annoying ads.
|
||||
</p>
|
||||
</li>
|
||||
<li className={styles.feature}>
|
||||
<svg
|
||||
aria-hidden='true'
|
||||
focusable='false'
|
||||
role='img'
|
||||
className={styles.feature__icon}
|
||||
>
|
||||
<use href='/svg/sprite.svg#icon-palette'></use>
|
||||
</svg>
|
||||
<h3
|
||||
className={`heading heading__tertiary ${styles.feature__heading}`}
|
||||
>
|
||||
Modern interface
|
||||
</h3>
|
||||
<p className={styles.feature__text}>
|
||||
Modern interface with curated colors supporting both dark and
|
||||
light themes.
|
||||
</p>
|
||||
</li>
|
||||
<li className={styles.feature}>
|
||||
<svg
|
||||
aria-hidden='true'
|
||||
focusable='false'
|
||||
role='img'
|
||||
className={styles.feature__icon}
|
||||
>
|
||||
<use href='/svg/sprite.svg#icon-responsive'></use>
|
||||
</svg>
|
||||
<h3
|
||||
className={`heading heading__tertiary ${styles.feature__heading}`}
|
||||
>
|
||||
Responsive design
|
||||
</h3>
|
||||
<p className={styles.feature__text}>
|
||||
Be it your small mobile or big computer screen, it's fully
|
||||
responsive.
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
<section id='faq' className={styles.faqs}>
|
||||
<h2 className={`heading heading__secondary ${styles.faqs__heading}`}>
|
||||
Questions you may have
|
||||
</h2>
|
||||
<div className={styles.faqs__list}>
|
||||
<details className={styles.faq}>
|
||||
<summary className={styles.faq__summary}>Why is it slow?</summary>
|
||||
<p className={styles.faq__description}>
|
||||
Whenever you request info about a movie/show on libremdb, 4
|
||||
trips are made(2 between your browser and libremdb's server, and
|
||||
2 between libremdb's server and IMDb's server) instead of the
|
||||
usual 2 trips when you visit a website. For this reason there's
|
||||
a noticable delay. This is a bit of inconvenience you'll have to
|
||||
face should you wish to use this website.
|
||||
</p>
|
||||
</details>
|
||||
<details className={styles.faq}>
|
||||
<summary className={styles.faq__summary}>
|
||||
It doesn't have all routes.
|
||||
</summary>
|
||||
<p className={styles.faq__description}>
|
||||
I'll implement more with time :)
|
||||
</p>
|
||||
</details>
|
||||
<details className={styles.faq}>
|
||||
<summary className={styles.faq__summary}>
|
||||
I see connection being made to some Amazon domains.
|
||||
</summary>
|
||||
<p className={styles.faq__description}>
|
||||
For now, images and videos are directly served from Amazon. If I
|
||||
have enough time in the future, I'll implement a way to serve
|
||||
the images from libremdb instead.
|
||||
</p>
|
||||
</details>
|
||||
<details className={styles.faq}>
|
||||
<summary className={styles.faq__summary}>
|
||||
Will Amazon track me then?
|
||||
</summary>
|
||||
<p className={styles.faq__description}>
|
||||
They may log your IP address, useragent, and other such
|
||||
identifiers. I'd recommend using a VPN, or accessing the website
|
||||
through TOR for mitigating this risk.
|
||||
</p>
|
||||
</details>
|
||||
<details className={styles.faq}>
|
||||
<summary className={styles.faq__summary}>
|
||||
Why not just use IMDb?
|
||||
</summary>
|
||||
<p className={styles.faq__description}>
|
||||
Refer to the{' '}
|
||||
<a className='link' href='#features'>
|
||||
features section
|
||||
</a>{' '}
|
||||
above.
|
||||
</p>
|
||||
</details>
|
||||
<details className={styles.faq}>
|
||||
<summary className={styles.faq__summary}>
|
||||
Why didn't you use other databases like TMDB or OMDb?
|
||||
</summary>
|
||||
<p className={styles.faq__description}>
|
||||
IMDb simply has superior dataset compared to all other
|
||||
alternatives. With that being said, I'd encourage you to check
|
||||
out those alternatives too.
|
||||
</p>
|
||||
</details>
|
||||
<details className={styles.faq}>
|
||||
<summary className={styles.faq__summary}>
|
||||
Your website name is quite, ehm, lame.
|
||||
</summary>
|
||||
<p className={styles.faq__description}>
|
||||
Let's just say I'm not very good at naming things.
|
||||
</p>
|
||||
</details>
|
||||
<details className={styles.faq}>
|
||||
<summary className={styles.faq__summary}>
|
||||
I have some ideas/features/suggestions.
|
||||
</summary>
|
||||
<p className={styles.faq__description}>
|
||||
That's great! I've a couple of{' '}
|
||||
<Link href='/contact'>
|
||||
<a className='link'>contact methods</a>
|
||||
</Link>
|
||||
. Send your beautiful suggestions(or complaints), or just drop a
|
||||
hi.
|
||||
</p>
|
||||
</details>
|
||||
</div>
|
||||
</section>
|
||||
</Layout>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default About;
|
49
src/pages/contact/index.tsx
Normal file
49
src/pages/contact/index.tsx
Normal file
@ -0,0 +1,49 @@
|
||||
import Meta from '../../components/Meta/Meta';
|
||||
import Layout from '../../layouts/Layout';
|
||||
|
||||
import styles from '../../styles/modules/pages/contact/contact.module.scss';
|
||||
|
||||
const Contact = () => {
|
||||
return (
|
||||
<>
|
||||
<Meta
|
||||
title='Contact'
|
||||
description='Contact page of libremdb, a free & open source IMDb front-end.'
|
||||
/>
|
||||
<Layout className=''>
|
||||
<section className={styles.contact}>
|
||||
<h1 className={`heading heading__primary ${styles.contact__heading}`}>
|
||||
Contact
|
||||
</h1>
|
||||
|
||||
<div className={styles.list}>
|
||||
<p className={styles.item}>
|
||||
You can use{' '}
|
||||
<a href='https://github.com/zyachel/libremdb' className='link'>
|
||||
GitHub
|
||||
</a>{' '}
|
||||
or{' '}
|
||||
<a href='https://codeberg.org/zyachel/libremdb' className='link'>
|
||||
Codeberg
|
||||
</a>{' '}
|
||||
for general issues, questions, or requests.
|
||||
</p>
|
||||
<p className={styles.item}>
|
||||
In case you wish to contact me personally, I'm reachable via{' '}
|
||||
<a className='link' href='https://matrix.to/#/@ninal:matrix.org'>
|
||||
[matrix]
|
||||
</a>{' '}
|
||||
and{' '}
|
||||
<a className='link' href='mailto:aricla@protonmail.com'>
|
||||
email
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</Layout>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Contact;
|
74
src/pages/privacy/index.tsx
Normal file
74
src/pages/privacy/index.tsx
Normal file
@ -0,0 +1,74 @@
|
||||
import Meta from '../../components/Meta/Meta';
|
||||
import Layout from '../../layouts/Layout';
|
||||
|
||||
import styles from '../../styles/modules/pages/privacy/privacy.module.scss';
|
||||
|
||||
const Privacy = () => {
|
||||
return (
|
||||
<>
|
||||
<Meta
|
||||
title='Privacy'
|
||||
description='Privacy policy of libremdb, a free & open source IMDb front-end.'
|
||||
/>
|
||||
<Layout className={styles.privacy}>
|
||||
<section className={styles.policy}>
|
||||
<h1 className={`heading heading__primary ${styles.policy__heading}`}>
|
||||
Privacy Policy
|
||||
</h1>
|
||||
<div className={styles.list}>
|
||||
<div className={styles.item}>
|
||||
<h2
|
||||
className={`heading heading__secondary ${styles.item__heading}`}
|
||||
>
|
||||
Information collected
|
||||
</h2>
|
||||
<p className={styles.item__text}>No information is collected.</p>
|
||||
</div>
|
||||
<div className={styles.item}>
|
||||
<h2
|
||||
className={`heading heading__secondary ${styles.item__heading}`}
|
||||
>
|
||||
Information stored in your browser
|
||||
</h2>
|
||||
<p className={styles.item__text}>
|
||||
A key named 'theme' is stored in Local Storage provided by your
|
||||
browser, if you ever override the default theme. To remove it,
|
||||
go to site data settings, and clear the data for this website.
|
||||
</p>
|
||||
<p className={styles.item__text}>
|
||||
To permamently disable libremdb from storing your theme
|
||||
prefrences, either turn off JavaScript or disable access to
|
||||
Local Storage for libremdb.
|
||||
</p>
|
||||
</div>
|
||||
<div className={styles.item}>
|
||||
<h2
|
||||
className={`heading heading__secondary ${styles.item__heading}`}
|
||||
>
|
||||
Information collected by other services
|
||||
</h2>
|
||||
<p className={styles.item__text}>
|
||||
libremdb connects to 'media-amazon.com' and 'media-imdb.com' for
|
||||
fetching images and videos. So, Amazon might log your IP
|
||||
address, and other information(such as http headers) sent by
|
||||
your browser.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer className={styles.metadata}>
|
||||
<p>
|
||||
Last updated on <time>10 september, 2022.</time>
|
||||
</p>
|
||||
<p>
|
||||
You can see the full revision history of this privacy policy on
|
||||
GitHub, or Codeberg.
|
||||
</p>
|
||||
</footer>
|
||||
</section>
|
||||
</Layout>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Privacy;
|
108
src/pages/title/[titleId]/index.tsx
Normal file
108
src/pages/title/[titleId]/index.tsx
Normal file
@ -0,0 +1,108 @@
|
||||
// external
|
||||
import { GetServerSideProps, GetStaticProps, GetStaticPaths } from 'next';
|
||||
import { useRouter } from 'next/router';
|
||||
// local
|
||||
import Meta from '../../../components/Meta/Meta';
|
||||
import Layout from '../../../layouts/Layout';
|
||||
import title from '../../../utils/fetchers/title';
|
||||
// components
|
||||
import ErrorInfo from '../../../components/Error/ErrorInfo';
|
||||
import Basic from '../../../components/title/Basic';
|
||||
import Media from '../../../components/title/Media';
|
||||
import Cast from '../../../components/title/Cast';
|
||||
import DidYouKnow from '../../../components/title/DidYouKnow';
|
||||
import Info from '../../../components/title/Info';
|
||||
import Reviews from '../../../components/title/Reviews';
|
||||
import MoreLikeThis from '../../../components/title/MoreLikeThis';
|
||||
// misc
|
||||
import Title from '../../../interfaces/shared/title';
|
||||
import { AppError } from '../../../interfaces/shared/error';
|
||||
// styles
|
||||
import styles from '../../../styles/modules/pages/title/title.module.scss';
|
||||
|
||||
type Props = { data: Title; error: null } | { error: AppError; data: null };
|
||||
|
||||
// TO-DO: make a wrapper page component to display errors, if present in props
|
||||
const TitleInfo = ({ data, error }: Props) => {
|
||||
const router = useRouter();
|
||||
|
||||
if (error)
|
||||
return <ErrorInfo message={error.message} statusCode={error.statusCode} />;
|
||||
|
||||
const info = {
|
||||
meta: data.meta,
|
||||
keywords: data.keywords,
|
||||
details: data.details,
|
||||
boxOffice: data.boxOffice,
|
||||
technicalSpecs: data.technicalSpecs,
|
||||
accolades: data.accolades,
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Meta
|
||||
title={`${data.basic.title} (${
|
||||
data.basic.releaseYear?.start || data.basic.type.name
|
||||
})`}
|
||||
description={data.basic.plot || undefined}
|
||||
/>
|
||||
|
||||
<Layout className={styles.title}>
|
||||
<Basic data={data.basic} className={styles.basic} />
|
||||
<Media className={styles.media} media={data.media} router={router} />
|
||||
<Cast className={styles.cast} cast={data.cast} />
|
||||
<div className={styles.textarea}>
|
||||
<DidYouKnow data={data.didYouKnow} />
|
||||
<Reviews reviews={data.reviews} router={router} />
|
||||
</div>
|
||||
<Info className={styles.infoarea} info={info} router={router} />
|
||||
<MoreLikeThis className={styles.related} data={data.moreLikeThis} />
|
||||
</Layout>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
// TO-DO: make a getServerSideProps wrapper for handling errors
|
||||
export const getServerSideProps: GetServerSideProps = async ctx => {
|
||||
const titleId = ctx.params!.titleId as string;
|
||||
|
||||
try {
|
||||
const data = await title(titleId);
|
||||
|
||||
return { props: { data, error: null } };
|
||||
} catch (error: any) {
|
||||
const { message, statusCode } = error;
|
||||
ctx.res.statusCode = statusCode;
|
||||
ctx.res.statusMessage = message;
|
||||
|
||||
return { props: { error: { message, statusCode }, data: null } };
|
||||
}
|
||||
};
|
||||
|
||||
export default TitleInfo;
|
||||
|
||||
// could've used getStaticProps instead of getServerSideProps, but meh.
|
||||
/*
|
||||
export const getStaticProps: GetStaticProps = async ctx => {
|
||||
const titleId = ctx.params!.titleId as string;
|
||||
try {
|
||||
const data = await title(titleId);
|
||||
|
||||
return {
|
||||
props: { data, error: null },
|
||||
revalidate: 60 * 60 * 24, // 1 day
|
||||
};
|
||||
} catch (error) {
|
||||
// console.log(error);
|
||||
|
||||
return { notFound: true };
|
||||
}
|
||||
};
|
||||
|
||||
export const getStaticPaths: GetStaticPaths = () => {
|
||||
return {
|
||||
paths: [{ params: { titleId: 'tt0133093' } }],
|
||||
fallback: 'blocking',
|
||||
};
|
||||
};
|
||||
*/
|
Reference in New Issue
Block a user