From 255fbb521f483e425239b5e353796aaf7a08e216 Mon Sep 17 00:00:00 2001 From: zyachel Date: Sun, 1 Jun 2025 13:07:02 +0000 Subject: [PATCH] fix(title): fix title route crash was due to an upstream change. also show ai summary, ratings distrubution, and multiple user reviews re https://github.com/zyachel/libremdb/issues/98 --- src/components/title/Reviews.tsx | 179 +- src/interfaces/misc/rawTitle.ts | 1515 +++++++++-------- .../components/title/reviews.module.scss | 80 +- src/utils/cleaners/title.ts | 62 +- 4 files changed, 1053 insertions(+), 783 deletions(-) diff --git a/src/components/title/Reviews.tsx b/src/components/title/Reviews.tsx index 0fe3e93..d0bebde 100644 --- a/src/components/title/Reviews.tsx +++ b/src/components/title/Reviews.tsx @@ -1,82 +1,127 @@ import { useRouter } from 'next/router'; import Link from 'next/link'; -import { Reviews } from 'src/interfaces/shared/title'; +import type { Reviews as TReviews } from 'src/interfaces/shared/title'; import { formatNumber } from 'src/utils/helpers'; import styles from 'src/styles/modules/components/title/reviews.module.scss'; type Props = { - reviews: Reviews; + reviews: TReviews; }; const Reviews = ({ reviews }: Props) => { + return ( +
+

Reviews

+ + + +
+

User Reviews

+ {reviews.featuredReviews ? ( +
    + {reviews.featuredReviews.map(featuredReview => ( +
  • +
    + + {featuredReview.review.summary} + +
    + +
    +
  • + ))} +
+ ) : ( +

No reviews yet.

+ )} +
+ + {reviews.ai?.summary && ( +
+ AI Summary +

+

    + {reviews.ai.themes.map(theme => ( +
  • {theme.text}
  • + ))} +
+
+ )} + + +
+ ); +}; + +export default Reviews; + +const RatingsDistribution = ({ ratings }: { ratings: Props['reviews']['ratingsDistribution'] }) => { + const maxRating = Math.max(...ratings.map(r => r.votes)); + + return ( +
+

Ratings Distribution

+ {ratings.length ? ( + + ) : ( +

No ratings yet.

+ )} +
+ ); +}; + +const ReviewStats = ({ reviews }: { reviews: Props['reviews'] }) => { const router = useRouter(); const { titleId } = router.query; return ( -
-

Reviews

- - {reviews.featuredReview && ( -
-
- - {reviews.featuredReview.review.summary} - -
-
-
-

- {reviews.featuredReview.rating && ( - Rated {reviews.featuredReview.rating}/10 - )} - - {' '} - by{' '} - - {reviews.featuredReview.reviewer.name} - - - on {reviews.featuredReview.date}. -

-

- - {formatNumber(reviews.featuredReview.votes.up)} upvotes - - - , {formatNumber(reviews.featuredReview.votes.down)} downvotes - -

-
-
- )} - -
-

- - - {formatNumber(reviews.numUserReviews)} User reviews - - -

-

- - - {formatNumber(reviews.numCriticReviews)} Critic reviews - - -

-

- - {reviews.metacriticScore} Metascore - -

-
-
+
+

+ + {formatNumber(reviews.numUserReviews)} User reviews + +

+

+ + {formatNumber(reviews.numCriticReviews)} Critic reviews + +

+

+ + {reviews.metacriticScore} Metascore + +

+
); }; -export default Reviews; diff --git a/src/interfaces/misc/rawTitle.ts b/src/interfaces/misc/rawTitle.ts index ffc4603..034aac9 100644 --- a/src/interfaces/misc/rawTitle.ts +++ b/src/interfaces/misc/rawTitle.ts @@ -2,7 +2,250 @@ export default interface RawTitle { props: { pageProps: { aboveTheFoldData: { + canHaveEpisodes: boolean; + canRate: { + isRatable: boolean; + }; + castPageTitle: { + edges: Array<{ + node: { + name: { + id: string; + nameText: { + text: string; + }; + }; + }; + }>; + }; + certificate?: { + rating: string; + }; + countriesOfOrigin?: { + countries: Array<{ + id: string; + }>; + }; + creatorsPageTitle: Array<{ + credits: Array<{ + name: { + nameText: { + text: string; + }; + }; + }>; + }>; + credits: { + total: number; + }; + criticReviewsTotal: { + total: number; + }; + directorsPageTitle: Array<{ + credits: Array<{ + name: { + id: string; + nameText: { + text: string; + }; + }; + }>; + }>; + engagementStatistics?: { + watchlistStatistics: { + displayableCount: { + text: string; + }; + }; + }; + externalLinks: { + total: number; + }; + featuredReviews: { + edges: Array<{ + node: { + author: { + nickName: string; + }; + authorRating?: number; + submissionDate: string; + summary: { + originalText: string; + }; + text: { + originalText: { + plainText: string; + }; + }; + }; + }>; + }; + genres: { + genres: Array<{ + id: string; + text: string; + }>; + }; id: string; + images: { + edges: Array<{ + node: { + id: string; + }; + }>; + total: number; + }; + interests: { + edges: Array<{ + node: { + id: string; + primaryText: { + text: string; + }; + }; + }>; + }; + isAdult: boolean; + keywords: { + edges: Array<{ + node: { + text: string; + }; + }>; + total: number; + }; + meta: { + canonicalId: string; + publicationStatus: string; + }; + metacritic?: { + metascore: { + score: number; + }; + }; + meterRanking?: { + currentRank: number; + rankChange: { + changeDirection: string; + difference: number; + }; + }; + originalTitleText: { + text: string; + }; + plot?: { + language?: { + id: string; + }; + plotText?: { + plainText: string; + }; + }; + plotContributionLink: { + url: string; + }; + primaryImage?: { + caption: { + plainText: string; + }; + height: number; + id: string; + url: string; + width: number; + }; + primaryVideos: { + edges: Array<{ + node: { + contentType: { + displayName: { + value: string; + }; + id: string; + }; + createdDate: string; + description?: { + language: string; + value: string; + }; + id: string; + isMature: boolean; + name: { + language: string; + value: string; + }; + playbackURLs: Array<{ + displayName: { + language: string; + value: string; + }; + url: string; + videoDefinition: string; + videoMimeType?: string; + }>; + previewURLs: Array<{ + displayName: { + language: string; + value: string; + }; + url: string; + videoDefinition: string; + videoMimeType: string; + }>; + primaryTitle: { + id: string; + originalTitleText: { + text: string; + }; + releaseYear: { + year: number; + }; + titleText: { + text: string; + }; + }; + recommendedTimedTextTrack: null; + runtime: { + value: number; + }; + thumbnail: { + height: number; + url: string; + width: number; + }; + timedTextTracks: Array; + }; + }>; + }; + principalCredits: Array<{ + category: { + id: string; + text: string; + }; + credits: Array<{ + attributes?: Array<{ + text: string; + }>; + name: { + id: string; + nameText: { + text: string; + }; + }; + }>; + totalCredits: number; + }>; + production: { + edges: Array<{ + node: { + company: { + companyText: { + text: string; + }; + id: string; + }; + }; + }>; + }; productionStatus: { currentProductionStage: { id: string; @@ -19,7 +262,33 @@ export default interface RawTitle { unrestrictedTotal: number; }; } | null; - canHaveEpisodes: boolean; + ratingsSummary: { + aggregateRating?: number; + voteCount: number; + }; + releaseDate: { + country: { + text: string; + }; + day: number; + month: number; + year: number; + }; + releaseYear: { + endYear: any; + year: number; + }; + reviews: { + total: number; + }; + runtime?: { + displayableProperty: { + value: { + plainText: string; + }; + }; + seconds: number; + }; series?: { episodeNumber: { episodeNumber: number; @@ -50,374 +319,93 @@ export default interface RawTitle { }; }; }; + subNavCredits: { + total: number; + }; + subNavFaqs: { + total: number; + }; + subNavReviews: { + total: number; + }; + subNavTopQuestions: { + total: number; + }; + subNavTrivia: { + total: number; + }; + titleGenres: { + genres: Array<{ + genre: { + text: string; + }; + }>; + }; titleText: { text: string; }; titleType: { - text: string; + canHaveEpisodes: boolean; + categories: Array<{ + value: string; + }>; + displayableProperty: { + value: { + plainText: string; + }; + }; id: string; - isSeries: boolean; isEpisode: boolean; - }; - originalTitleText: { + isSeries: boolean; text: string; }; - certificate?: { - rating: string; - }; - releaseYear?: { - year: number; - endYear: any; - }; - releaseDate?: { - day: number; - month: number; - year: number; - }; - runtime?: { - seconds: number; - }; - canRate: { - isRatable: boolean; - }; - ratingsSummary: { - aggregateRating?: number; - voteCount: number; - }; - meterRanking?: { - currentRank: number; - rankChange: { - changeDirection: string; - difference: number; - }; - }; - primaryImage?: { - id: string; - width: number; - height: number; - url: string; - caption: { - plainText: string; - }; - }; - images: { - total: number; - }; - videos: { - total: number; - }; - primaryVideos: { - edges: Array<{ - node: { - id: string; - isMature: boolean; - contentType: { - id: string; - displayName: { - value: string; - }; - }; - thumbnail: { - url: string; - height: number; - width: number; - }; - runtime: { - value: number; - }; - description?: { - value: string; - language: string; - }; - name: { - value: string; - language: string; - }; - playbackURLs: Array<{ - displayName: { - value: string; - language: string; - }; - mimeType?: string; - url: string; - }>; - previewURLs: Array<{ - displayName: { - value: string; - language: string; - }; - mimeType: string; - url: string; - }>; - }; - }>; - }; - externalLinks: { - total: number; - }; - metacritic?: { - metascore: { - score: number; - }; - }; - keywords: { - total: number; - edges: Array<{ - node: { - text: string; - }; - }>; - }; - genres: { - genres: Array<{ - text: string; - id: string; - }>; - }; - plot?: { - plotText?: { - plainText: string; - }; - language?: { - id: string; - }; - }; - plotContributionLink: { - url: string; - }; - credits: { - total: number; - }; - principalCredits: Array<{ - totalCredits: number; - category: { - text: string; - id: string; - }; - credits: Array<{ - name: { - nameText: { - text: string; - }; - id: string; - }; - attributes?: Array<{ - text: string; - }>; - }>; - }>; - reviews: { - total: number; - }; - criticReviewsTotal: { - total: number; - }; triviaTotal: { total: number; }; - meta: { - canonicalId: string; - publicationStatus: string; - }; - castPageTitle: { - edges: Array<{ - node: { - name: { - nameText: { - text: string; - }; - }; - }; - }>; - }; - creatorsPageTitle: Array<{ - credits: Array<{ - name: { - nameText: { - text: string; - }; - }; - }>; - }>; - directorsPageTitle: Array<{ - credits: Array<{ - name: { - nameText: { - text: string; - }; - }; - }>; - }>; - countriesOfOrigin?: { - countries: Array<{ - id: string; - }>; - }; - production: { - edges: Array<{ - node: { - company: { - id: string; - companyText: { - text: string; - }; - }; - }; - }>; - }; - featuredReviews: { - edges: Array<{ - node: { - author: { - nickName: string; - }; - summary: { - originalText: string; - }; - text: { - originalText: { - plainText: string; - }; - }; - authorRating: number; - submissionDate: string; - }; - }>; - }; - }; - mainColumnData: { - id: string; - wins: { - total: number; - }; - nominationsExcludeWins: { - total: number; - }; - prestigiousAwardSummary?: { - nominations: number; - wins: number; - award: { - text: string; - id: string; - event: { - id: string; - }; - }; - }; - ratingsSummary: { - topRanking?: { - id: string; - text: { - value: string; - }; - rank: number; - }; - }; - episodes?: { - episodes: { - total: number; - }; - seasons: Array<{ - number: number; - }>; - years: Array<{ - year: number; - }>; - totalEpisodes: { - total: number; - }; - topRated: { - edges: Array<{ - node: { - ratingsSummary: { - aggregateRating: number; - }; - }; - }>; - }; - }; videos: { total: number; }; - videoStrip: { - edges: Array<{ - node: { - id: string; - contentType: { - displayName: { - value: string; - }; - }; - name: { - value: string; - }; - runtime: { - value: number; - }; - thumbnail: { - height: number; - url: string; - width: number; - }; - }; - }>; - }; - titleMainImages: { - total: number; - edges: Array<{ - node: { - id: string; - url: string; - caption: { - plainText: string; - }; - height: number; - width: number; - }; - }>; - }; - productionStatus: { - currentProductionStage: { - id: string; - text: string; + }; + mainColumnData: { + aggregateRatingsBreakdown: { + histogram: { + histogramValues: Array<{ + rating: number; + voteCount: number; + }>; }; - productionStatusHistory?: Array<{ - status: { - id: string; + }; + akas: { + edges: Array<{ + node: { text: string; }; }>; - restriction?: { - restrictionReason: Array; - }; }; - primaryImage?: { - id: string; + alternateVersions: { + edges: Array<{ + node: { + text: { + plaidHtml: string; + }; + }; + }>; + total: number; }; - imageUploadLink?: { - url: string; - }; - titleType: { - id: string; - canHaveEpisodes: boolean; + canHaveEpisodes: boolean; + canRate: { + isRatable: boolean; }; cast: { edges: Array<{ node: { - name: { - id: string; - nameText: { - text: string; - }; - primaryImage?: { - url: string; - width: number; - height: number; - }; - }; attributes?: Array<{ text: string; }>; + category: { + id: string; + text: string; + }; characters?: Array<{ name: string; }>; @@ -428,184 +416,48 @@ export default interface RawTitle { endYear: number; }; }; - }; - }>; - }; - creators: Array<{ - totalCredits: number; - category: { - text: string; - }; - credits: Array<{ - name: { - id: string; - nameText: { - text: string; - }; - }; - attributes: any; - }>; - }>; - directors: Array<{ - totalCredits: number; - category: { - text: string; - }; - credits: Array<{ - name: { - id: string; - nameText: { - text: string; - }; - }; - attributes: any; - }>; - }>; - writers: Array<{ - totalCredits: number; - category: { - text: string; - }; - credits: Array<{ - name: { - id: string; - nameText: { - text: string; - }; - }; - attributes?: Array<{ - text: string; - }>; - }>; - }>; - isAdult: boolean; - moreLikeThisTitles: { - edges: Array<{ - node: { - id: string; - titleText: { - text: string; - }; - titleType: { + name: { id: string; - text: string; + nameText: { + text: string; + }; + primaryImage?: { + height: number; + url: string; + width: number; + }; }; - originalTitleText: { - text: string; - }; - primaryImage?: { - id: string; - width: number; - height: number; - url: string; - }; - releaseYear?: { - year: number; - endYear?: number; - }; - ratingsSummary: { - aggregateRating?: number; - voteCount: number; - }; - runtime?: { - seconds: number; - }; - certificate?: { - rating: string; - }; - canRate: { - isRatable: boolean; - }; - titleGenres?: { - genres: Array<{ - genre: { - text: string; - }; - }>; - }; - canHaveEpisodes: boolean; }; }>; - }; - triviaTotal: { total: number; }; - trivia: { - edges: Array<{ - node: { - text: { - plaidHtml: string; - }; - trademark: any; - relatedNames: any; - }; - }>; - }; - goofsTotal: { + companies: { total: number; }; - goofs: { - edges: Array<{ - node: { - text: { - plaidHtml: string; - }; - }; - }>; - }; - quotesTotal: { - total: number; - }; - quotes: { - edges: Array<{ - node: { - lines: Array<{ - characters?: Array<{ - character: string; - name?: { - id: string; - }; - }>; - text: string; - stageDirection?: string; - }>; - }; - }>; - }; - crazyCredits: { - edges: Array<{ - node: { - text: { - plaidHtml: string; - }; - }; - }>; - }; - alternateVersions: { - total: number; - edges: Array<{ - node: { - text: { - plaidHtml: string; - }; - }; - }>; - }; connections: { edges: Array<{ node: { associatedTitle: { id: string; + originalTitleText: { + text: string; + }; releaseYear: { year: number; }; + series?: { + series: { + originalTitleText: { + text: string; + }; + titleText: { + text: string; + }; + }; + }; titleText: { text: string; }; - originalTitleText: { - text: string; - }; - series: any; }; category: { text: string; @@ -613,195 +465,6 @@ export default interface RawTitle { }; }>; }; - soundtrack: { - edges: Array<{ - node: { - text: string; - comments?: Array<{ - plaidHtml: string; - }>; - }; - }>; - }; - titleText: { - text: string; - }; - originalTitleText: { - text: string; - }; - releaseYear?: { - year: number; - }; - reviews: { - total: number; - }; - featuredReviews: { - edges: Array<{ - node: { - id: string; - author: { - nickName: string; - userId: string; - }; - summary: { - originalText: string; - }; - text: { - originalText: { - plaidHtml: string; - }; - }; - authorRating: number; - submissionDate: string; - helpfulness: { - upVotes: number; - downVotes: number; - }; - }; - }>; - }; - canRate: { - isRatable: boolean; - }; - iframeAddReviewLink: { - url: string; - }; - faqsTotal: { - total: number; - }; - faqs: { - edges: Array<{ - node: { - id: string; - question: { - plainText: string; - }; - }; - }>; - }; - releaseDate?: { - day: number; - month: number; - year: number; - country: { - id: string; - text: string; - }; - }; - countriesOfOrigin?: { - countries: Array<{ - id: string; - text: string; - }>; - }; - detailsExternalLinks: { - edges: Array<{ - node: { - url: string; - label: string; - externalLinkRegion?: { - text: string; - }; - }; - }>; - total: number; - }; - spokenLanguages: { - spokenLanguages: Array<{ - id: string; - text: string; - }>; - }; - akas: { - edges: Array<{ - node: { - text: string; - }; - }>; - }; - filmingLocations: { - edges: Array<{ - node: { - text: string; - }; - }>; - total: number; - }; - production: { - edges: Array<{ - node: { - company: { - id: string; - companyText: { - text: string; - }; - }; - }; - }>; - }; - companies: { - total: number; - }; - productionBudget?: { - budget: { - amount: number; - currency: string; - }; - }; - lifetimeGross?: { - total: { - amount: number; - currency: string; - }; - }; - openingWeekendGross?: { - gross: { - total: { - amount: number; - currency: string; - }; - }; - weekendEndDate: string; - }; - worldwideGross?: { - total: { - amount: number; - currency: string; - }; - }; - technicalSpecifications: { - soundMixes: { - items: Array<{ - id: string; - text: string; - attributes: Array; - }>; - }; - aspectRatios: { - items: Array<{ - aspectRatio: string; - attributes: Array; - }>; - }; - colorations: { - items: Array<{ - conceptId: string; - text: string; - attributes: Array; - }>; - }; - }; - runtime?: { - seconds: number; - }; - series?: { - series: { - runtime?: { - seconds: number; - }; - }; - }; - canHaveEpisodes: boolean; contributionQuestions: { contributionLink: { url: string; @@ -828,7 +491,479 @@ export default interface RawTitle { }; }>; }; + countriesDetails: { + countries: Array<{ + id: string; + text: string; + }>; + }; + crazyCredits: { + edges: Array<{ + node: { + text: { + plaidHtml: string; + }; + }; + }>; + }; + creators: Array<{ + name: { + id: string; + nameText: { + text: string; + }; + }; + attributes: any; + }>; + detailsExternalLinks: { + edges: Array<{ + node: { + externalLinkRegion?: { text: string }; + label: string; + url: string; + }; + }>; + total: number; + }; + directors: Array<{ + category: { + id: string; + text: string; + }; + credits: Array<{ + name: { + id: string; + nameText: { + text: string; + }; + }; + attributes?: Array<{ text: string }>; + }>; + totalCredits: number; + }>; + episodes?: { + episodes: { + total: number; + }; + seasons: Array<{ + number: number; + }>; + years: Array<{ + year: number; + }>; + totalEpisodes: { + total: number; + }; + topRated: { + edges: Array<{ + node: { + ratingsSummary: { + aggregateRating: number; + }; + }; + }>; + }; + }; + faqs: { + edges: Array<{ + node: { + id: string; + question: { + plainText: string; + }; + }; + }>; + total: number; + }; + featuredReviews: { + edges: Array<{ + node: { + author: { + userId: string; + username: { + text: string; + }; + }; + authorRating?: number; + id: string; + summary: { + originalText: string; + }; + text: { + originalText: { + plaidHtml: string; + }; + }; + }; + }>; + }; + filmingLocations: { + edges: Array<{ + node: { + attributes: Array; + location: string; + text: string; + }; + }>; + total: number; + }; + goofs: { + edges: Array<{ + node: { + text: { + plaidHtml: string; + }; + }; + }>; + }; + goofsTotal: { + total: number; + }; + id: string; + inIframeAddLink: { + url: string; + }; + isAdult: boolean; + lifetimeGross?: { + total: { + amount: number; + currency: string; + }; + }; + moreLikeThisTitles: { + edges: Array<{ + node: { + canHaveEpisodes: boolean; + canRate: { + isRatable: boolean; + }; + certificate?: { + rating: string; + }; + id: string; + originalTitleText: { + text: string; + }; + primaryImage?: { + caption?: { + plainText: string; + }; + height: number; + id: string; + url: string; + width: number; + }; + ratingsSummary: { + aggregateRating?: number; + voteCount: number; + }; + releaseYear?: { + endYear?: number; + year: number; + }; + runtime?: { + seconds: number; + }; + titleGenres?: { + genres: Array<{ + genre: { + text: string; + }; + }>; + }; + titleText: { + text: string; + }; + titleType: { + canHaveEpisodes: boolean; + displayableProperty: { + value: { + plainText: string; + }; + }; + id: string; + text: string; + }; + }; + }>; + }; + nominationsExcludeWins: { + total: number; + }; + notInIframeAddLink: { + url: string; + }; + openingWeekendGross?: { + gross: { + total: { + amount: number; + currency: string; + }; + }; + weekendEndDate: string; + }; + originalTitleText: { + text: string; + }; + prestigiousAwardSummary?: { + award: { + event: { + id: string; + }; + id: string; + text: string; + }; + nominations: number; + wins: number; + }; + primaryImage?: { + caption: { + plainText: string; + }; + height: number; + id: string; + url: string; + width: number; + }; + production: { + edges: Array<{ + node: { + company: { + companyText: { + text: string; + }; + id: string; + }; + }; + }>; + }; + productionBudget?: { + budget: { + amount: number; + currency: string; + }; + }; + productionStatus: { + currentProductionStage: { + id: string; + text: string; + }; + productionStatusHistory?: Array<{ + status: { + id: string; + text: string; + }; + }>; + restriction?: { + restrictionReason: Array; + }; + }; + quotes: { + edges: Array<{ + node: { + lines: Array<{ + characters?: Array<{ + character: string; + name?: { + id: string; + }; + }>; + stageDirection?: string; + text: string; + }>; + }; + }>; + }; + quotesTotal: { + total: number; + }; + ratingsSummary: { + aggregateRating: number; + notificationText?: string; + topRanking?: { + id: string; + rank: number; + text: { + value: string; + }; + }; + voteCount: number; + }; + releaseDate?: { + country: { + id: string; + text: string; + }; + day: number; + month: number; + year: number; + }; + reviews: { + total: number; + }; + reviewSummary?: { + overall: { + medium: { + value: { + plaidHtml: string; + }; + }; + }; + themes: Array<{ + label: { + value: string; + }; + sentiment: string; + themeId: string; + }>; + }; + runtime: { + seconds: number; + }; + series?: { + series: { + runtime?: { + seconds: number; + }; + }; + }; + soundtrack: { + edges: Array<{ + node: { + comments?: Array<{ + plaidHtml: string; + }>; + text: string; + }; + }>; + }; + spokenLanguages: { + spokenLanguages: Array<{ + id: string; + text: string; + }>; + }; + technicalSpecifications: { + aspectRatios: { + items: Array<{ + aspectRatio: string; + attributes: Array; + }>; + }; + colorations: { + items: Array<{ + attributes: Array; + conceptId: string; + text: string; + }>; + }; + soundMixes: { + items: Array<{ + attributes: Array; + id: string; + text: string; + }>; + }; + }; + titleMainImages: { + edges: Array<{ + node: { + caption: { + plainText: string; + }; + height: number; + id: string; + url: string; + width: number; + }; + }>; + total: number; + }; + titleText: { + text: string; + }; + titleType: { + canHaveEpisodes: boolean; + id: string; + }; + topQuestions: { + edges: Array<{ + node: { + attributeId: string; + question: { + plainText: string; + }; + }; + }>; + total: number; + }; + trivia: { + edges: Array<{ + node: { + relatedNames: any; + text: { + plaidHtml: string; + }; + trademark: any; + }; + }>; + }; + triviaTotal: { + total: number; + }; + videos: { + total: number; + }; + videoStrip: { + edges: Array<{ + node: { + contentType: { + displayName: { + value: string; + }; + }; + id: string; + name: { + value: string; + }; + runtime: { + value: number; + }; + thumbnail: { + height: number; + url: string; + width: number; + }; + }; + }>; + }; + wins: { + total: number; + }; + worldwideGross?: { + total: { + amount: number; + currency: string; + }; + }; + writers: Array<{ + category: { + id: string; + text: string; + }; + credits: Array<{ + name: { + id: string; + nameText: { + text: string; + }; + }; + attributes?: Array<{ text: string }>; + }>; + totalCredits: number; + }>; }; + tconst: string; }; }; } diff --git a/src/styles/modules/components/title/reviews.module.scss b/src/styles/modules/components/title/reviews.module.scss index 685748a..22b58c2 100644 --- a/src/styles/modules/components/title/reviews.module.scss +++ b/src/styles/modules/components/title/reviews.module.scss @@ -1,26 +1,94 @@ .reviews { display: grid; gap: var(--comp-whitespace); +} - &__reviewContainer { - // background-color: antiquewhite; +.ratingsDistribution { + overflow: hidden; + + ul { + list-style: none; + display: flex; + gap: var(--spacer-2); + overflow-x: auto; + padding-block: var(--spacer-1); } - &__stats { + li { + text-align: center; display: flex; + flex-direction: column; + height: 5em; + align-items: center; + flex-shrink: 0; + + &::before { + display: block; + content: ''; + + height: var(--bar-height); + margin-top: auto; + background-color: var(--clr-fill); + width: var(--spacer-6); + border-radius: var(--spacer-0); + } + + span { + font-weight: var(--fw-bold); + + > span { + font-weight: initial; + font-size: 0.9em; + color: var(--clr-text-muted); + } + } + } +} + +.reviewAi { + summary { + cursor: pointer; + } + p { + padding-block: var(--spacer-2); + } + ul { + list-style: none; + display: flex; + gap: var(--spacer-1); flex-wrap: wrap; - gap: var(--spacer-2); + + li { + padding: var(--spacer-1); + background-color: var(--clr-bg-muted); + border-radius: var(--spacer-0); + } + } +} + +.reviewStats { + display: flex; + flex-wrap: wrap; + gap: var(--spacer-2); +} + +.userReviews { + + &__list { + padding-block-start: var(--spacer-1); + display: grid; + gap: var(--spacer-1); + list-style: none; } } .review { &__summary { - font-size: calc(var(--fs-5) * 1.1); cursor: pointer; } &__text, &__metadata { - padding-top: var(--spacer-2); + padding-top: var(--spacer-1); } } diff --git a/src/utils/cleaners/title.ts b/src/utils/cleaners/title.ts index 7902925..2e95661 100644 --- a/src/utils/cleaners/title.ts +++ b/src/utils/cleaners/title.ts @@ -12,6 +12,7 @@ const cleanTitle = (rawData: RawTitle) => { titleId: main.id, basic: { id: main.id, + isAdult: main.isAdult, title: main.titleText.text, // ...(main.originalTitleText.text.toLowerCase() !== // main.titleText.text.toLowerCase() && { @@ -50,6 +51,10 @@ const cleanTitle = (rawData: RawTitle) => { id: genre.id, text: genre.text, })), + interests: main.interests.edges.map(interest => ({ + id: interest.node.id, + text: interest.node.primaryText.text, + })), plot: main.plot?.plotText?.plainText || null, primaryCrew: main.principalCredits.map(type => ({ type: { category: type.category.text, id: type.category.id }, @@ -84,7 +89,7 @@ const cleanTitle = (rawData: RawTitle) => { caption: main.primaryVideos.edges[0].node.description?.value ?? null, urls: main.primaryVideos.edges[0].node.playbackURLs.map(url => ({ resolution: url.displayName.value, - mimeType: url.mimeType ?? null, + mimeType: url.videoMimeType ?? null, url: url.url, })), }, @@ -122,6 +127,9 @@ const cleanTitle = (rawData: RawTitle) => { }), topRating: misc.ratingsSummary.topRanking?.rank || null, }, + watchlist: { + text: main.engagementStatistics?.watchlistStatistics.displayableCount.text || null, + }, meta: { // for tv episode ...(main.series && { @@ -208,25 +216,35 @@ const cleanTitle = (rawData: RawTitle) => { metacriticScore: main.metacritic?.metascore.score || null, numCriticReviews: main.criticReviewsTotal.total, numUserReviews: misc.reviews.total, - ...(misc.featuredReviews.edges.length && { - featuredReview: { - id: misc.featuredReviews.edges[0].node.id, - reviewer: { - id: misc.featuredReviews.edges[0].node.author.userId, - name: misc.featuredReviews.edges[0].node.author.nickName, - }, - rating: misc.featuredReviews.edges[0].node.authorRating, - date: formatDate(misc.featuredReviews.edges[0].node.submissionDate), - votes: { - up: misc.featuredReviews.edges[0].node.helpfulness.upVotes, - down: misc.featuredReviews.edges[0].node.helpfulness.downVotes, - }, - review: { - summary: misc.featuredReviews.edges[0].node.summary.originalText, - html: misc.featuredReviews.edges[0].node.text.originalText.plaidHtml, - }, + ratingsDistribution: + misc.aggregateRatingsBreakdown.histogram?.histogramValues.map(v => ({ + rating: v.rating, + votes: v.voteCount, + })) || [], + ...(misc.reviewSummary && { + ai: { + summary: misc.reviewSummary.overall.medium.value.plaidHtml, + themes: misc.reviewSummary.themes.map(t => ({ + text: t.label.value, + id: t.themeId, + sentiment: t.sentiment as 'POSITIVE' | 'NEGATIVE', + })), }, }), + ...(misc.featuredReviews.edges.length && { + featuredReviews: misc.featuredReviews.edges.map(featuredReview => ({ + id: featuredReview.node.id, + reviewer: { + id: featuredReview.node.author.userId, + name: featuredReview.node.author.username.text, + }, + rating: featuredReview.node.authorRating, + review: { + summary: featuredReview.node.summary.originalText, + html: featuredReview.node.text.originalText.plaidHtml, + }, + })), + }), }, details: { ...(misc.releaseDate && { @@ -242,8 +260,8 @@ const cleanTitle = (rawData: RawTitle) => { }, }, }), - ...(misc.countriesOfOrigin && { - countriesOfOrigin: misc.countriesOfOrigin.countries.map(country => ({ + ...(misc.countriesDetails && { + countriesOfOrigin: misc.countriesDetails.countries.map(country => ({ id: country.id, text: country.text, })), @@ -353,6 +371,10 @@ const cleanTitle = (rawData: RawTitle) => { }, genres: title.node.titleGenres?.genres.map(genre => genre.genre.text) ?? null, })), + faqs: { + questions: misc.faqs.edges.map(q => ({ question: q.node.question, id: q.node.id })), + total: misc.faqs.total, + }, }; return cleanData;