feat: fetch images from media proxy on frontend
This commit is contained in:
parent
b7ee6863e5
commit
dba2ba5aa4
@ -1,22 +1,27 @@
|
|||||||
import { Fragment } from 'react';
|
import { Fragment } from 'react'
|
||||||
import Image from 'next/future/image';
|
import Image from 'next/future/image'
|
||||||
import Link from 'next/link';
|
import Link from 'next/link'
|
||||||
|
|
||||||
import { formatNumber, formatTime, modifyIMDbImg } from '../../utils/helpers';
|
import {
|
||||||
import { Basic } from '../../interfaces/shared/title';
|
formatNumber,
|
||||||
import styles from '../../styles/modules/components/title/basic.module.scss';
|
formatTime,
|
||||||
|
getProxiedIMDbImgUrl,
|
||||||
|
modifyIMDbImg,
|
||||||
|
} from '../../utils/helpers'
|
||||||
|
import { Basic } from '../../interfaces/shared/title'
|
||||||
|
import styles from '../../styles/modules/components/title/basic.module.scss'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
className: string;
|
className: string
|
||||||
data: Basic;
|
data: Basic
|
||||||
};
|
}
|
||||||
|
|
||||||
const Basic = ({ data, className }: Props) => {
|
const Basic = ({ data, className }: Props) => {
|
||||||
const titleType = data.type.id;
|
const titleType = data.type.id
|
||||||
const releaseTime =
|
const releaseTime =
|
||||||
titleType === 'tvSeries'
|
titleType === 'tvSeries'
|
||||||
? `${data.releaseYear?.start}-${data.releaseYear?.end || 'present'}`
|
? `${data.releaseYear?.start}-${data.releaseYear?.end || 'present'}`
|
||||||
: data.releaseYear?.start;
|
: data.releaseYear?.start
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
@ -29,7 +34,8 @@ const Basic = ({ data, className }: Props) => {
|
|||||||
className={styles.imageContainer}
|
className={styles.imageContainer}
|
||||||
style={{
|
style={{
|
||||||
backgroundImage:
|
backgroundImage:
|
||||||
data.poster && `url(${modifyIMDbImg(data.poster.url, 300)})`,
|
data.poster &&
|
||||||
|
`url(${getProxiedIMDbImgUrl(modifyIMDbImg(data.poster.url, 300))})`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{data.poster ? (
|
{data.poster ? (
|
||||||
@ -39,11 +45,11 @@ const Basic = ({ data, className }: Props) => {
|
|||||||
alt={data.poster.caption}
|
alt={data.poster.caption}
|
||||||
priority
|
priority
|
||||||
fill
|
fill
|
||||||
sizes='300px'
|
sizes="300px"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<svg className={styles.image__NA}>
|
<svg className={styles.image__NA}>
|
||||||
<use href='/svg/sprite.svg#icon-image-slash' />
|
<use href="/svg/sprite.svg#icon-image-slash" />
|
||||||
</svg>
|
</svg>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -51,7 +57,7 @@ const Basic = ({ data, className }: Props) => {
|
|||||||
<h1 className={`${styles.title} heading heading__primary`}>
|
<h1 className={`${styles.title} heading heading__primary`}>
|
||||||
{data.title}
|
{data.title}
|
||||||
</h1>
|
</h1>
|
||||||
<ul className={styles.meta} aria-label='quick facts'>
|
<ul className={styles.meta} aria-label="quick facts">
|
||||||
{data.status.id !== 'released' && (
|
{data.status.id !== 'released' && (
|
||||||
<li className={styles.meta__text}>{data.status.text}</li>
|
<li className={styles.meta__text}>{data.status.text}</li>
|
||||||
)}
|
)}
|
||||||
@ -72,7 +78,7 @@ const Basic = ({ data, className }: Props) => {
|
|||||||
<p className={styles.rating}>
|
<p className={styles.rating}>
|
||||||
<span className={styles.rating__num}>{data.ratings.avg}</span>
|
<span className={styles.rating__num}>{data.ratings.avg}</span>
|
||||||
<svg className={styles.rating__icon}>
|
<svg className={styles.rating__icon}>
|
||||||
<use href='/svg/sprite.svg#icon-rating'></use>
|
<use href="/svg/sprite.svg#icon-rating"></use>
|
||||||
</svg>
|
</svg>
|
||||||
<span className={styles.rating__text}> Avg. rating</span>
|
<span className={styles.rating__text}> Avg. rating</span>
|
||||||
</p>
|
</p>
|
||||||
@ -81,7 +87,7 @@ const Basic = ({ data, className }: Props) => {
|
|||||||
{formatNumber(data.ratings.numVotes)}
|
{formatNumber(data.ratings.numVotes)}
|
||||||
</span>
|
</span>
|
||||||
<svg className={styles.rating__icon}>
|
<svg className={styles.rating__icon}>
|
||||||
<use href='/svg/sprite.svg#icon-like-dislike'></use>
|
<use href="/svg/sprite.svg#icon-like-dislike"></use>
|
||||||
</svg>
|
</svg>
|
||||||
<span className={styles.rating__text}> No. of votes</span>
|
<span className={styles.rating__text}> No. of votes</span>
|
||||||
</p>
|
</p>
|
||||||
@ -93,7 +99,7 @@ const Basic = ({ data, className }: Props) => {
|
|||||||
{formatNumber(data.ranking.position)}
|
{formatNumber(data.ranking.position)}
|
||||||
</span>
|
</span>
|
||||||
<svg className={styles.rating__icon}>
|
<svg className={styles.rating__icon}>
|
||||||
<use href='/svg/sprite.svg#icon-graph-rising'></use>
|
<use href="/svg/sprite.svg#icon-graph-rising"></use>
|
||||||
</svg>
|
</svg>
|
||||||
<span className={styles.rating__text}>
|
<span className={styles.rating__text}>
|
||||||
{' '}
|
{' '}
|
||||||
@ -130,7 +136,7 @@ const Basic = ({ data, className }: Props) => {
|
|||||||
<span className={styles.overview__text}>{data.plot || '-'}</span>
|
<span className={styles.overview__text}>{data.plot || '-'}</span>
|
||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
{data.primaryCrew.map(crewType => (
|
{data.primaryCrew.map((crewType) => (
|
||||||
<p className={styles.crewType} key={crewType.type.id}>
|
<p className={styles.crewType} key={crewType.type.id}>
|
||||||
<span className={styles.crewType__heading}>
|
<span className={styles.crewType__heading}>
|
||||||
{`${crewType.type.category}: `}
|
{`${crewType.type.category}: `}
|
||||||
@ -147,7 +153,7 @@ const Basic = ({ data, className }: Props) => {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
export default Basic;
|
export default Basic
|
||||||
|
@ -1,37 +1,39 @@
|
|||||||
import Image from 'next/future/image';
|
import Image from 'next/future/image'
|
||||||
import Link from 'next/link';
|
import Link from 'next/link'
|
||||||
import { NextRouter } from 'next/router';
|
import { NextRouter } from 'next/router'
|
||||||
import { Media } from '../../interfaces/shared/title';
|
import { Media } from '../../interfaces/shared/title'
|
||||||
import { modifyIMDbImg } from '../../utils/helpers';
|
import { getProxiedIMDbImgUrl, modifyIMDbImg } from '../../utils/helpers'
|
||||||
|
|
||||||
import styles from '../../styles/modules/components/title/media.module.scss';
|
import styles from '../../styles/modules/components/title/media.module.scss'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
className: string;
|
className: string
|
||||||
media: Media;
|
media: Media
|
||||||
router: NextRouter;
|
router: NextRouter
|
||||||
};
|
}
|
||||||
|
|
||||||
const Media = ({ className, media, router }: Props) => {
|
const Media = ({ className, media, router }: Props) => {
|
||||||
return (
|
return (
|
||||||
<div className={`${className} ${styles.media}`}>
|
<div className={`${className} ${styles.media}`}>
|
||||||
{(media.trailer || !!media.videos.total) && (
|
{(media.trailer || !!media.videos.total) && (
|
||||||
<section className={styles.videos}>
|
<section className={styles.videos}>
|
||||||
<h2 className='heading heading__secondary'>Videos</h2>
|
<h2 className="heading heading__secondary">Videos</h2>
|
||||||
|
|
||||||
<div className={styles.videos__container}>
|
<div className={styles.videos__container}>
|
||||||
{media.trailer && (
|
{media.trailer && (
|
||||||
<div key={router.asPath} className={styles.trailer}>
|
<div key={router.asPath} className={styles.trailer}>
|
||||||
<video
|
<video
|
||||||
aria-label='trailer video'
|
aria-label="trailer video"
|
||||||
// it's a relatively new tag. hence jsx-all1 complains
|
// it's a relatively new tag. hence jsx-all1 complains
|
||||||
aria-description={media.trailer.caption}
|
aria-description={media.trailer.caption}
|
||||||
controls
|
controls
|
||||||
playsInline
|
playsInline
|
||||||
poster={modifyIMDbImg(media.trailer.thumbnail)}
|
poster={getProxiedIMDbImgUrl(
|
||||||
|
modifyIMDbImg(media.trailer.thumbnail)
|
||||||
|
)}
|
||||||
className={styles.trailer__video}
|
className={styles.trailer__video}
|
||||||
>
|
>
|
||||||
{media.trailer.urls.map(source => (
|
{media.trailer.urls.map((source) => (
|
||||||
<source
|
<source
|
||||||
key={source.url}
|
key={source.url}
|
||||||
type={source.mimeType}
|
type={source.mimeType}
|
||||||
@ -44,15 +46,15 @@ const Media = ({ className, media, router }: Props) => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{!!media.videos.total &&
|
{!!media.videos.total &&
|
||||||
media.videos.videos.map(video => (
|
media.videos.videos.map((video) => (
|
||||||
<Link href={`/video/${video.id}`} key={video.id}>
|
<Link href={`/video/${video.id}`} key={video.id}>
|
||||||
<a className={styles.video}>
|
<a className={styles.video}>
|
||||||
<Image
|
<Image
|
||||||
className={styles.video__img}
|
className={styles.video__img}
|
||||||
src={modifyIMDbImg(video.thumbnail)}
|
src={modifyIMDbImg(video.thumbnail)}
|
||||||
alt=''
|
alt=""
|
||||||
fill
|
fill
|
||||||
sizes='400px'
|
sizes="400px"
|
||||||
/>
|
/>
|
||||||
<p className={styles.video__caption}>
|
<p className={styles.video__caption}>
|
||||||
{video.caption} ({video.runtime}s)
|
{video.caption} ({video.runtime}s)
|
||||||
@ -65,16 +67,16 @@ const Media = ({ className, media, router }: Props) => {
|
|||||||
)}
|
)}
|
||||||
{!!media.images.total && (
|
{!!media.images.total && (
|
||||||
<section className={styles.images}>
|
<section className={styles.images}>
|
||||||
<h2 className='heading heading__secondary'>Images</h2>
|
<h2 className="heading heading__secondary">Images</h2>
|
||||||
<div className={styles.images__container}>
|
<div className={styles.images__container}>
|
||||||
{media.images.images.map(image => (
|
{media.images.images.map((image) => (
|
||||||
<figure key={image.id} className={styles.image}>
|
<figure key={image.id} className={styles.image}>
|
||||||
<Image
|
<Image
|
||||||
className={styles.image__img}
|
className={styles.image__img}
|
||||||
src={modifyIMDbImg(image.url)}
|
src={modifyIMDbImg(image.url)}
|
||||||
alt=''
|
alt=""
|
||||||
fill
|
fill
|
||||||
sizes='400px'
|
sizes="400px"
|
||||||
/>
|
/>
|
||||||
<figcaption className={styles.image__caption}>
|
<figcaption className={styles.image__caption}>
|
||||||
{image.caption.plainText}
|
{image.caption.plainText}
|
||||||
@ -85,6 +87,6 @@ const Media = ({ className, media, router }: Props) => {
|
|||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
export default Media;
|
export default Media
|
||||||
|
@ -14,7 +14,7 @@ export default async function handler(
|
|||||||
|
|
||||||
if (!mediaUrl) {
|
if (!mediaUrl) {
|
||||||
res.status(400)
|
res.status(400)
|
||||||
res.send(null)
|
res.end()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ export default async function handler(
|
|||||||
mediaUrlParsed = new URL(mediaUrl)
|
mediaUrlParsed = new URL(mediaUrl)
|
||||||
} catch {
|
} catch {
|
||||||
res.status(400)
|
res.status(400)
|
||||||
res.send(null)
|
res.end()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,13 +33,13 @@ export default async function handler(
|
|||||||
|
|
||||||
if (!mediaDomain.endsWith('media-amazon.com')) {
|
if (!mediaDomain.endsWith('media-amazon.com')) {
|
||||||
res.status(400)
|
res.status(400)
|
||||||
res.send(null)
|
res.end()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mediaUrlParsed.protocol !== 'https:') {
|
if (mediaUrlParsed.protocol !== 'https:') {
|
||||||
res.status(400)
|
res.status(400)
|
||||||
res.send(null)
|
res.end()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,7 +54,7 @@ export default async function handler(
|
|||||||
|
|
||||||
if (!validExtension) {
|
if (!validExtension) {
|
||||||
res.status(400)
|
res.status(400)
|
||||||
res.send(null)
|
res.end()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,6 +79,7 @@ export default async function handler(
|
|||||||
|
|
||||||
if (!mediaRes.ok) {
|
if (!mediaRes.ok) {
|
||||||
res.status(mediaRes.status)
|
res.status(mediaRes.status)
|
||||||
|
res.end()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ import { AppError } from '../../../interfaces/shared/error'
|
|||||||
// styles
|
// styles
|
||||||
import styles from '../../../styles/modules/pages/title/title.module.scss'
|
import styles from '../../../styles/modules/pages/title/title.module.scss'
|
||||||
import Head from 'next/head'
|
import Head from 'next/head'
|
||||||
|
import { getProxiedIMDbImgUrl } from '../../../utils/helpers'
|
||||||
|
|
||||||
type Props = { data: Title; error: null } | { error: AppError; data: null }
|
type Props = { data: Title; error: null } | { error: AppError; data: null }
|
||||||
|
|
||||||
@ -50,7 +51,11 @@ const TitleInfo = ({ data, error }: Props) => {
|
|||||||
<Head>
|
<Head>
|
||||||
<meta
|
<meta
|
||||||
title="og:image"
|
title="og:image"
|
||||||
content={data.basic.poster?.url || '/icon-512.png'}
|
content={
|
||||||
|
data.basic.poster?.url
|
||||||
|
? getProxiedIMDbImgUrl(data.basic.poster?.url)
|
||||||
|
: '/icon-512.png'
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</Head>
|
</Head>
|
||||||
<Layout className={styles.title}>
|
<Layout className={styles.title}>
|
||||||
|
@ -53,6 +53,10 @@ export const modifyIMDbImg = (url: string, widthInPx = 600) => {
|
|||||||
return url.replaceAll('.jpg', `UX${widthInPx}.jpg`);
|
return url.replaceAll('.jpg', `UX${widthInPx}.jpg`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getProxiedIMDbImgUrl = (url: string) => {
|
||||||
|
return `/api/media_proxy?url=${encodeURIComponent(url)}`;
|
||||||
|
}
|
||||||
|
|
||||||
export const AppError = class extends Error {
|
export const AppError = class extends Error {
|
||||||
constructor(message: string, public statusCode: number, cause?: any) {
|
constructor(message: string, public statusCode: number, cause?: any) {
|
||||||
super(message, cause);
|
super(message, cause);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user