-
{t("library:gameCharacteristics")}
-
-
}
- description={game.involvedCompanies
- .filter((c: { developer: string }) => c.developer)
- .map((c: { companyName: string }) => c.companyName)
- .join(", ")}
- />
-
}
- description={game.involvedCompanies
- .filter((c: { publisher: string }) => c.publisher)
- .map((c: { companyName: string }) => c.companyName)
- .join(", ")}
- />
-
}
- description={game.platforms.map((p: { name: string }) => p.name).join(", ")}
- />
-
}
- description={game.themes.join(", ")}
- />
-
}
- description={game.gameModes.map((m: { name: string }) => m.name).join(", ")}
- />
-
}
- description={game.playerPerspectives.map((p: { name: string }) => p.name).join(", ")}
+
+
+
{game.summary}
+
{t("library:genres")}
+
g.name)}
+ getLabel={(g) => getGenreLabel(t, g)}
/>
- {game.gameEngines.length > 0 && (
+
+
+
+
{t("library:gameCharacteristics")}
+
}
- description={game.gameEngines.map((e: { name: string }) => e.name).join(", ")}
+ title={t("library:developers")}
+ icon={}
+ description={game.involvedCompanies
+ .filter((c: { developer: string }) => c.developer)
+ .map((c: { companyName: string }) => c.companyName)
+ .join(", ")}
/>
- )}
+ }
+ description={game.involvedCompanies
+ .filter((c: { publisher: string }) => c.publisher)
+ .map((c: { companyName: string }) => c.companyName)
+ .join(", ")}
+ />
+ }
+ description={game.platforms.map((p: { name: string }) => p.name).join(", ")}
+ />
+ }
+ description={game.themes.join(", ")}
+ />
+ }
+ description={game.gameModes.map((m: { name: string }) => m.name).join(", ")}
+ />
+ }
+ description={game.playerPerspectives.map((p: { name: string }) => p.name).join(", ")}
+ />
+ {game.gameEngines.length > 0 && (
+ }
+ description={game.gameEngines.map((e: { name: string }) => e.name).join(", ")}
+ />
+ )}
+
-
-
-
{t("library:communityStatistics")}
-
-
-
-
- {game.videos.map((video: { checksum: string; videoId: string; name: string }) => (
-
-
-
- ))}
- {game.screenshots.map((screenshot: { checksum: string; imageId: string }) => (
-
-
-
- ))}
-
-
-
-
-
-
+
+
{t("library:communityStatistics")}
+
+
+
+
+ {game.videos.map((video: { checksum: string; videoId: string; name: string }) => (
+
+
+
+ ))}
+ {game.screenshots.map((screenshot: { checksum: string; imageId: string }) => (
+
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+ {(() => {
+ const { nodes, edges } = buildRelationsData(game);
+ return ;
+ })()}
+
+ {!screenshotsQuery.isLoading && !screenshotsQuery.isError && (
+
+
+ {screenshots?.map((screenshot: { checksum: string; imageId: string }) => (
+
+
+
+ ))}
+
+
+ )}
+
+
+
+
+
+
+ {t("library:reviews")} ({reviews?.total ?? 0})
+
+ {isAuthenticated && (
+
+ )}
+
+ {(reviews?.total ?? 0) >= 1 ? (
-
-
-
-
-
-
-
- {(() => {
- const { nodes, edges } = buildRelationsData(game);
- return ;
- })()}
-
- {!screenshotsQuery.isLoading && !screenshotsQuery.isError && (
-
-
- {screenshots?.map((screenshot: { checksum: string; imageId: string }) => (
-
-
-
- ))}
-
-
+ ) : (
+
+
+
+
+
+ {t("library:noReviews")}
+ {t("library:noReviewsDescription")}
+
+
)}
-
-
+
+ >
);
}
diff --git a/src/routes/manga/$slug.tsx b/src/routes/manga/$slug.tsx
index 58fab2f..46eadb4 100644
--- a/src/routes/manga/$slug.tsx
+++ b/src/routes/manga/$slug.tsx
@@ -1,7 +1,7 @@
import { Icon } from "@iconify/react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { createFileRoute, Link } from "@tanstack/react-router";
-import type { ReactNode } from "react";
+import { type ReactNode, useState } from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { Grid } from "@/components/layouts/grid.tsx";
@@ -20,6 +20,8 @@ import { ErrorComponent } from "@/components/shared/error.tsx";
import { LoadingDetails } from "@/components/shared/loadings/details.tsx";
import { MangaModal } from "@/components/shared/modals/manga";
import { RefreshData } from "@/components/shared/modals/refresh-data";
+import { Button } from "@/components/ui/button";
+import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "@/components/ui/empty";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { type ApiTypes, api, apiEndpoints } from "@/lib/api.ts";
import { useSession } from "@/lib/auth.ts";
@@ -86,6 +88,7 @@ function MangaDetailsRoute() {
const session = useSession();
const isAuthenticated = !!session?.data?.session;
+ const [moreOpen, setMoreOpen] = useState(false);
if (isLoading || reviewsData.isLoading) return
;
if (isError || reviewsData.isError || !manga) return
;
@@ -137,6 +140,8 @@ function MangaDetailsRoute() {
subtitle={manga.published.string}
description={manga.synopsis}
triggerLabel={t("library:moreOptions")}
+ open={moreOpen}
+ onOpenChange={setMoreOpen}
>
@@ -239,140 +244,190 @@ function MangaDetailsRoute() {
);
return (
-
-
- {manga.title}
-
+ <>
+
+
+ {manga.title}
+
-
- {reviews.total >= 1 && (
-
-
-
-
-
-
-
+
+ {reviews.total >= 1 && (
+
+
+
+
+
+
+
+
+
{rating}
+
+ ({reviews.total} {t("library:reviews")})
+
-
{rating}
-
- ({reviews.total} {t("library:reviews")})
-
-
- )}
-
-
-
-
-
- {t("library:info")}
- {t("library:relations")}
- {t("library:characters")}
- {reviews.total >= 1 && (
-
- {t("library:reviews")} ({reviews.total})
-
- )}
- {t("library:lists")} (30)
-
+ )}
-
-
-
{t("library:genres")}
- getGenreLabel(t, g)} />
-
-
-
{t("library:synopsis")}
-
{manga.synopsis}
+
+
+
+ {t("library:info")}
+ {t("library:relations")}
+ {t("library:characters")}
+ {t("library:lists")} (30)
+
+
+
+
{manga.synopsis}
+
{t("library:genres")}
+
getGenreLabel(t, g)} />
+
-
-
{t("library:mangaCharacteristics")}
-
- {manga.type && (
- }
- description={manga.type}
- />
- )}
- {manga.numberOfChapters && (
- }
- description={manga.numberOfChapters}
- />
- )}
- {manga.numberOfVolumes && (
- }
- description={manga.numberOfVolumes}
- />
- )}
- {manga.themes.length >= 1 && (
- }
- description={manga.themes.map((theme: string, index: number) => (
-
-
- {getGenreLabel(t, theme)}
+
+
{t("library:mangaCharacteristics")}
+
+ {manga.type && (
+ }
+ description={manga.type}
+ />
+ )}
+ {manga.numberOfChapters && (
+ }
+ description={manga.numberOfChapters}
+ />
+ )}
+ {manga.numberOfVolumes && (
+ }
+ description={manga.numberOfVolumes}
+ />
+ )}
+ {manga.themes.length >= 1 && (
+ }
+ description={manga.themes.map((theme: string, index: number) => (
+
+
+ {getGenreLabel(t, theme)}
+
+ {index < manga.themes.length - 1 && ", "}
+
+ ))}
+ />
+ )}
+ {manga.authors.length >= 1 && (
+ }
+ description={manga.authors.map((au: { name: string; malId: number }, index: number) => (
+
+ {au.name}
+ {index < manga.authors.length - 1 && "; "}
- {index < manga.themes.length - 1 && ", "}
-
- ))}
- />
- )}
- {manga.authors.length >= 1 && (
- }
- description={manga.authors.map((au: { name: string; malId: number }, index: number) => (
-
- {au.name}
- {index < manga.authors.length - 1 && "; "}
-
- ))}
- />
- )}
- {manga.serializations && (
- }
- description={manga.serializations.map((sz: { name: string; malId: number }, index: number) => (
-
- {sz.name}
- {index < manga.serializations.length - 1 && ", "}
-
- ))}
+ ))}
+ />
+ )}
+ {manga.serializations && (
+ }
+ description={manga.serializations.map((sz: { name: string; malId: number }, index: number) => (
+
+ {sz.name}
+ {index < manga.serializations.length - 1 && ", "}
+
+ ))}
+ />
+ )}
+
+
+
+
+
{t("library:communityStatistics")}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {manga.characters?.map((character: { name: string; imageUrl: string }) => (
+
- )}
+ ))}
-
+
+
+
-
-
{t("library:communityStatistics")}
-
-
-
-
-
-
-
+
+
+
+ {t("library:reviews")} ({reviews.total})
+
+ {isAuthenticated && (
+
+ )}
+
+ {reviews.total >= 1 ? (
-
-
-
-
-
-
-
-
- {manga.characters?.map((character: { name: string; imageUrl: string }) => (
-
- ))}
-
-
-
-
+ ) : (
+
+
+
+
+
+ {t("library:noReviews")}
+ {t("library:noReviewsDescription")}
+
+
+ )}
+
+ >
);
}
diff --git a/src/routes/movie/$slug.tsx b/src/routes/movie/$slug.tsx
index 881c7f2..259d3b6 100644
--- a/src/routes/movie/$slug.tsx
+++ b/src/routes/movie/$slug.tsx
@@ -22,6 +22,8 @@ import { LoadingDetails } from "@/components/shared/loadings/details.tsx";
import { MovieModal } from "@/components/shared/modals/movie";
import { RefreshData } from "@/components/shared/modals/refresh-data";
import { StarRating } from "@/components/shared/star-rating";
+import { Button } from "@/components/ui/button";
+import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from "@/components/ui/carousel";
import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "@/components/ui/empty";
import { ImageZoom } from "@/components/ui/image-zoom";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
@@ -328,12 +330,13 @@ function MovieDetailsRoute() {
);
return (
-
-
- {movie.title}
-
+ <>
+
+
+ {movie.title}
+
- {/* {movie?.belongsToCollection?.name && (
+ {/* {movie?.belongsToCollection?.name && (
)} */}
-
-
-
-
{rating}
-
- ({reviews.total} {t("library:reviews")})
-
+
+
+
+ {rating}
+
+ ({reviews.total} {t("library:reviews")})
+
+
-
-
-
-
- {t("library:info")}
- {t("library:cast")}
- {movie.backdrops.length >= 1 && {t("library:medias")}}
-
- {t("library:reviews")} ({reviews?.total ?? 0})
-
-
- {t("library:lists")} ({listsQuery.data?.total ?? 0})
-
- {t("comments:title")}
-
-
-
-
-
{t("library:genres")}
-
getGenreLabel(t, g)} />
+
+
+
+ {t("library:info")}
+ {t("library:cast")}
+
+ {t("library:lists")} ({listsQuery.data?.total ?? 0})
+
+ {t("comments:title")}
+
-
-
-
{t("library:synopsis")}
-
-
{movie.overview}
+
+
+
{movie.overview}
+
{t("library:genres")}
+
getGenreLabel(t, g)} />
-
-
-
{t("library:movieCharacteristics")}
-
- }
- description={
-
- Tom Gormican
-
- }
- />
- {movie.budget > 0 && (
- }
- description={new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format(
- movie.budget,
- )}
- />
- )}
- {movie.revenue > 0 && (
+
+
{t("library:movieCharacteristics")}
+
}
- description={new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format(
- movie.revenue,
- )}
- />
- )}
- {movie?.spokenLanguages[0]?.name && (
- }
- description={movie?.spokenLanguages[0]?.name}
- />
- )}
- {movie?.productionCompanies?.length >= 1 && (
- }
+ title={t("library:directors")}
+ icon={}
description={
-
- {movie.productionCompanies[0].name}
- {movie.productionCompanies.length > 1 && (
-
-
-
- +{movie.productionCompanies.length - 1}
-
-
-
-
- {movie.productionCompanies.slice(1).map((pc: { name: string }) => (
- - {pc.name}
- ))}
-
-
-
- )}
-
+
+ Tom Gormican
+
}
/>
- )}
- {movie.runtime > 0 && (
- }
- description={`${movie.runtime} min`}
- />
- )}
-
-
+ {movie.budget > 0 && (
+ }
+ description={new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format(
+ movie.budget,
+ )}
+ />
+ )}
+ {movie.revenue > 0 && (
+ }
+ description={new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format(
+ movie.revenue,
+ )}
+ />
+ )}
+ {movie?.spokenLanguages[0]?.name && (
+ }
+ description={movie?.spokenLanguages[0]?.name}
+ />
+ )}
+ {movie?.productionCompanies?.length >= 1 && (
+ }
+ description={
+
+ {movie.productionCompanies[0].name}
+ {movie.productionCompanies.length > 1 && (
+
+
+
+ +{movie.productionCompanies.length - 1}
+
+
+
+
+ {movie.productionCompanies.slice(1).map((pc: { name: string }) => (
+ - {pc.name}
+ ))}
+
+
+
+ )}
+
+ }
+ />
+ )}
+ {movie.runtime > 0 && (
+ }
+ description={`${movie.runtime} min`}
+ />
+ )}
+
+
-
-
{t("library:communityStatistics")}
-
-
+
+
{t("library:communityStatistics")}
+
+
- {movie.trailerId && (
-
)}
diff --git a/src/routes/game/$slug.tsx b/src/routes/game/$slug.tsx
index 5b79496..3f3d786 100644
--- a/src/routes/game/$slug.tsx
+++ b/src/routes/game/$slug.tsx
@@ -542,6 +542,7 @@ function GameDetailsRoute() {
src={video.videoId}
allowFullScreen
className="w-full inset-0 absolute h-full"
+ sandbox="allow-scripts allow-same-origin allow-presentation allow-popups"
title={video.name}
/>
diff --git a/src/routes/manga/$slug.tsx b/src/routes/manga/$slug.tsx
index ac537f5..1cad054 100644
--- a/src/routes/manga/$slug.tsx
+++ b/src/routes/manga/$slug.tsx
@@ -341,7 +341,7 @@ function MangaDetailsRoute() {
{rating}
- ({reviews.total} {t("library:reviews")})
+ ({reviews?.total ?? 0} {t("library:reviews")})
)}
@@ -407,7 +407,7 @@ function MangaDetailsRoute() {
title={t("library:authors")}
icon={}
description={manga.authors.map((au: { name: string; malId: number }, index: number) => (
-
+
{au.name}
{index < manga.authors.length - 1 && "; "}
diff --git a/src/routes/movie/$slug.tsx b/src/routes/movie/$slug.tsx
index ba008da..b32ca3f 100644
--- a/src/routes/movie/$slug.tsx
+++ b/src/routes/movie/$slug.tsx
@@ -417,7 +417,7 @@ function MovieDetailsRoute() {
{rating}
- ({reviews.total} {t("library:reviews")})
+ ({reviews?.total ?? 0} {t("library:reviews")})
@@ -560,6 +560,7 @@ function MovieDetailsRoute() {
src={`https://youtube.com/embed/${movie.trailerId}`}
allowFullScreen
className="absolute inset-0 w-full h-full"
+ sandbox="allow-scripts allow-same-origin allow-presentation allow-popups"
title="Trailer"
/>
diff --git a/src/routes/tv/$slug.tsx b/src/routes/tv/$slug.tsx
index 25791cd..3c41192 100644
--- a/src/routes/tv/$slug.tsx
+++ b/src/routes/tv/$slug.tsx
@@ -609,7 +609,7 @@ function TVShowDetailsPage() {