diff --git a/apps/web/src/app/[lang]/[username]/_components/user-items-list.tsx b/apps/web/src/app/[lang]/[username]/_components/user-items-list.tsx index 0c42d0609..1a51ea71f 100644 --- a/apps/web/src/app/[lang]/[username]/_components/user-items-list.tsx +++ b/apps/web/src/app/[lang]/[username]/_components/user-items-list.tsx @@ -7,6 +7,7 @@ import { useEffect } from 'react' import { useInView } from 'react-intersection-observer' import { v4 } from 'uuid' import { getUserItems, useGetUserItemsInfinite } from '@/api/user-items' +import { PosterFallback } from '@/components/poster-fallback' import { useLanguage } from '@/context/language' import { useSession } from '@/context/session' import { tmdbImage } from '@/utils/tmdb/image' @@ -70,7 +71,7 @@ export function UserItemsList({ filters }: UserItemsProps) { key={id} href={`/${language}/${mediaType === 'MOVIE' ? 'movies' : 'tv-series'}/${tmdbId}`} > - {posterPath && ( + {posterPath ? ( {title} + ) : ( + )} ))} diff --git a/apps/web/src/app/[lang]/[username]/stats/_most_watched-series.tsx b/apps/web/src/app/[lang]/[username]/stats/_most_watched-series.tsx index f144a51f3..035f6578c 100644 --- a/apps/web/src/app/[lang]/[username]/stats/_most_watched-series.tsx +++ b/apps/web/src/app/[lang]/[username]/stats/_most_watched-series.tsx @@ -51,12 +51,10 @@ export function MostWatchedSeries() { key={id} > - {posterPath && ( - - )} +

{episodes} {dictionary.episodes} diff --git a/apps/web/src/app/[lang]/lists/[id]/_components/list-items/list-item-card.tsx b/apps/web/src/app/[lang]/lists/[id]/_components/list-items/list-item-card.tsx index f91960943..c23f1db0a 100644 --- a/apps/web/src/app/[lang]/lists/[id]/_components/list-items/list-item-card.tsx +++ b/apps/web/src/app/[lang]/lists/[id]/_components/list-items/list-item-card.tsx @@ -5,6 +5,7 @@ import { CSS } from '@dnd-kit/utilities' import Image from 'next/image' import { Link } from 'next-view-transitions' import type { GetListItemsByListId200Item } from '@/api/endpoints.schemas' +import { PosterFallback } from '@/components/poster-fallback' import { useLanguage } from '@/context/language' import { cn } from '@/lib/utils' import { tmdbImage } from '@/utils/tmdb/image' @@ -43,7 +44,7 @@ export const ListItemCard = ({ {...props} >

- {posterPath && ( + {posterPath ? ( {title} + ) : ( + )}
@@ -65,7 +68,7 @@ export const ListItemCard = ({ {...props} >
- {posterPath && ( + {posterPath ? ( {title} + ) : ( + )}
{ const movie = await tmdb.movies.details(id, language) + const backdropUrl = movie.backdrop_path + ? tmdbImage(movie.backdrop_path) + : undefined + const posterUrl = movie.poster_path ? tmdbImage(movie.poster_path) : undefined + const structuredDataImage = + backdropUrl ?? posterUrl ?? `${APP_URL}/logo-black.png` return (
@@ -35,12 +42,12 @@ export const MovieDetails = async ({ id, language }: MovieDetailsProps) => { - +
diff --git a/apps/web/src/app/[lang]/onboarding/_components/onboarding-swiper.tsx b/apps/web/src/app/[lang]/onboarding/_components/onboarding-swiper.tsx index 816d2162d..cfd250f25 100644 --- a/apps/web/src/app/[lang]/onboarding/_components/onboarding-swiper.tsx +++ b/apps/web/src/app/[lang]/onboarding/_components/onboarding-swiper.tsx @@ -4,6 +4,7 @@ import { useInfiniteQuery } from '@tanstack/react-query' import { AnimatePresence, motion, type PanInfo } from 'framer-motion' import { Bookmark, Check, Eye, Pointer, X as XIcon } from 'lucide-react' import Image from 'next/image' +import { PosterFallback } from '@/components/poster-fallback' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { tmdb } from '@/services/tmdb' import type { Language } from '@/types/languages' @@ -194,7 +195,7 @@ function ExitCard({ style={{ zIndex: 20 }} >
- {movie.poster_path && ( + {movie.poster_path ? ( {movie.title} + ) : ( + )}
@@ -568,7 +571,7 @@ export const OnboardingSwiper = ({ lang }: OnboardingSwiperProps) => { zIndex: 3 - stackIdx, }} > - {movie.poster_path && ( + {movie.poster_path ? ( {movie.title} { className="object-cover" sizes="280px" /> + ) : ( + )}
) diff --git a/apps/web/src/app/[lang]/people/[id]/_credits-page.tsx b/apps/web/src/app/[lang]/people/[id]/_credits-page.tsx index ba0240f48..4dfaf74a5 100644 --- a/apps/web/src/app/[lang]/people/[id]/_credits-page.tsx +++ b/apps/web/src/app/[lang]/people/[id]/_credits-page.tsx @@ -79,12 +79,10 @@ export function CreditsPage({ roles, credits }: CreditsPageProps) { - {credit.poster_path && ( - - )} + diff --git a/apps/web/src/app/[lang]/tv-series/[id]/_components/tv-serie-details.tsx b/apps/web/src/app/[lang]/tv-series/[id]/_components/tv-serie-details.tsx index 59943e8aa..fa5a6da99 100644 --- a/apps/web/src/app/[lang]/tv-series/[id]/_components/tv-serie-details.tsx +++ b/apps/web/src/app/[lang]/tv-series/[id]/_components/tv-serie-details.tsx @@ -5,6 +5,7 @@ import { BreadcrumbJsonLd, TvSeriesJsonLd } from '@/components/structured-data' import { tmdb } from '@/services/tmdb' import type { Language } from '@/types/languages' import { tmdbImage } from '@/utils/tmdb/image' +import { APP_URL } from '../../../../../../constants' import { TvSerieInfos } from './tv-serie-infos' import { TvSerieTabs } from './tv-serie-tabs' @@ -15,6 +16,14 @@ type TvSerieDetailsProps = { export const TvSerieDetails = async ({ id, language }: TvSerieDetailsProps) => { const tvSerie = await tmdb.tv.details(id, language) + const backdropUrl = tvSerie.backdrop_path + ? tmdbImage(tvSerie.backdrop_path) + : undefined + const posterUrl = tvSerie.poster_path + ? tmdbImage(tvSerie.poster_path) + : undefined + const structuredDataImage = + backdropUrl ?? posterUrl ?? `${APP_URL}/logo-black.png` return (
@@ -34,10 +43,10 @@ export const TvSerieDetails = async ({ id, language }: TvSerieDetailsProps) => { - +
diff --git a/apps/web/src/components/banner/banner.tsx b/apps/web/src/components/banner/banner.tsx index df0ccd9a1..6469e6c9a 100644 --- a/apps/web/src/components/banner/banner.tsx +++ b/apps/web/src/components/banner/banner.tsx @@ -1,27 +1,56 @@ import type { ComponentProps } from 'react' import { cn } from '@/lib/utils' +import { PosterFallback } from '../poster-fallback' type BannerProps = { url?: string + posterUrl?: string + title?: string } & ComponentProps<'div'> -export const Banner = ({ url, className, ...props }: BannerProps) => { +export const Banner = ({ + url, + posterUrl, + title, + className, + ...props +}: BannerProps) => { + const backgroundUrl = url || posterUrl + const isPosterAsFallback = !url && Boolean(posterUrl) + return (
-
+ {backgroundUrl ? ( + <> +
+ + {isPosterAsFallback && ( +
+ )} + + ) : ( + + )}
) } diff --git a/apps/web/src/components/command-search/command-search-items.tsx b/apps/web/src/components/command-search/command-search-items.tsx index a80fd4e48..3bc685bd4 100644 --- a/apps/web/src/components/command-search/command-search-items.tsx +++ b/apps/web/src/components/command-search/command-search-items.tsx @@ -8,6 +8,7 @@ import { HoverCardPortal } from '@radix-ui/react-hover-card' import Image from 'next/image' import { Link } from 'next-view-transitions' import type { GetUsersSearch200UsersItem } from '@/api/endpoints.schemas' +import { PosterFallback } from '@/components/poster-fallback' import type { MovieWithMediaType, PersonWithMediaType, @@ -59,13 +60,15 @@ export const CommandSearchMovie = ({ - {item.poster_path && ( + {item.poster_path ? ( {item.title} + ) : ( + )} @@ -116,13 +119,15 @@ export const CommandSearchTvSerie = ({ - {item.poster_path && ( + {item.poster_path ? ( {item.name} + ) : ( + )} diff --git a/apps/web/src/components/full-review/full-review.tsx b/apps/web/src/components/full-review/full-review.tsx index fb408a333..dc8ac50d0 100644 --- a/apps/web/src/components/full-review/full-review.tsx +++ b/apps/web/src/components/full-review/full-review.tsx @@ -12,6 +12,7 @@ import Image from 'next/image' import { Link } from 'next-view-transitions' import { useState } from 'react' import type { GetDetailedReviews200ReviewsItem } from '@/api/endpoints.schemas' +import { PosterFallback } from '@/components/poster-fallback' import { useLanguage } from '@/context/language' import { cn } from '@/lib/utils' import { locale } from '@/utils/date/locale' @@ -58,8 +59,10 @@ export const FullReview = ({ review }: FullReviewProps) => {
- {posterPath && ( + {posterPath ? ( {title} + ) : ( + )}
diff --git a/apps/web/src/components/list-command/list-command-movies.tsx b/apps/web/src/components/list-command/list-command-movies.tsx index 0129da79a..88f7f9b21 100644 --- a/apps/web/src/components/list-command/list-command-movies.tsx +++ b/apps/web/src/components/list-command/list-command-movies.tsx @@ -10,6 +10,7 @@ import { Skeleton } from '@plotwist/ui/components/ui/skeleton' import { HoverCardPortal } from '@radix-ui/react-hover-card' import { Minus, Plus } from 'lucide-react' import Image from 'next/image' +import { PosterFallback } from '@/components/poster-fallback' import { Link } from 'next-view-transitions' import { v4 } from 'uuid' import { ItemHoverCard } from '@/components/item-hover-card' @@ -96,13 +97,15 @@ export const ListCommandMovies = ({ - {movie.poster_path && ( + {movie.poster_path ? ( {movie.title} + ) : ( + )} diff --git a/apps/web/src/components/list-command/list-command-tv.tsx b/apps/web/src/components/list-command/list-command-tv.tsx index 1d7c5fd72..7438d8afc 100644 --- a/apps/web/src/components/list-command/list-command-tv.tsx +++ b/apps/web/src/components/list-command/list-command-tv.tsx @@ -8,6 +8,7 @@ import { Skeleton } from '@plotwist/ui/components/ui/skeleton' import { HoverCardPortal } from '@radix-ui/react-hover-card' import { Minus, Plus } from 'lucide-react' import Image from 'next/image' +import { PosterFallback } from '@/components/poster-fallback' import { Link } from 'next-view-transitions' import { v4 } from 'uuid' import { ItemHoverCard } from '@/components/item-hover-card' @@ -94,13 +95,15 @@ export const ListCommandTv = ({ - {tvSerie.poster_path && ( + {tvSerie.poster_path ? ( {tvSerie.name} + ) : ( + )} diff --git a/apps/web/src/components/poster-card/poster-card.tsx b/apps/web/src/components/poster-card/poster-card.tsx index 8620298ca..6a9aa6aaf 100644 --- a/apps/web/src/components/poster-card/poster-card.tsx +++ b/apps/web/src/components/poster-card/poster-card.tsx @@ -1,16 +1,31 @@ import { Skeleton } from '@plotwist/ui/components/ui/skeleton' import NextImage, { type ImageProps } from 'next/image' import { type ComponentProps, forwardRef } from 'react' +import { PosterFallback } from '@/components/poster-fallback' const Root = forwardRef>((props, ref) => { return
}) Root.displayName = 'Root' +function hasValidPosterSrc(src: ImageProps['src']): boolean { + if (!src) return false + if (typeof src === 'string') { + return !src.endsWith('/null') && !src.endsWith('/undefined') && !src.endsWith('/') + } + return true +} + const Image = (props: ImageProps) => { + const valid = hasValidPosterSrc(props.src) + return (
- + {valid ? ( + + ) : ( + + )}
) } diff --git a/apps/web/src/components/poster-fallback/index.ts b/apps/web/src/components/poster-fallback/index.ts new file mode 100644 index 000000000..dc7ee1ba5 --- /dev/null +++ b/apps/web/src/components/poster-fallback/index.ts @@ -0,0 +1 @@ +export * from './poster-fallback' diff --git a/apps/web/src/components/poster-fallback/poster-fallback.tsx b/apps/web/src/components/poster-fallback/poster-fallback.tsx new file mode 100644 index 000000000..1b3af8d0e --- /dev/null +++ b/apps/web/src/components/poster-fallback/poster-fallback.tsx @@ -0,0 +1,60 @@ +import { cn } from '@/lib/utils' + +type PosterFallbackProps = { + title?: string + className?: string +} + +const PlotWistMark = ({ className }: { className?: string }) => ( + +) + +export const PosterFallback = ({ title, className }: PosterFallbackProps) => { + return ( +
+ + + {title && ( +

+ {title} +

+ )} +
+ ) +} diff --git a/apps/web/src/components/poster/poster.tsx b/apps/web/src/components/poster/poster.tsx index ad5d6d701..894e8f2e3 100644 --- a/apps/web/src/components/poster/poster.tsx +++ b/apps/web/src/components/poster/poster.tsx @@ -1,6 +1,6 @@ -import { Image as LucideImage } from 'lucide-react' import Image from 'next/image' import type { ComponentProps } from 'react' +import { PosterFallback } from '@/components/poster-fallback' import { cn } from '@/lib/utils' import { tmdbImage } from '@/utils/tmdb/image' @@ -28,7 +28,7 @@ export const Poster = ({ url, alt, className, ...props }: PosterProps) => { src={tmdbImage(url)} /> ) : ( - + )}
)