🚧 wip: share genre tags UI for reuse
This commit is contained in:
@ -1,4 +1,42 @@
|
|||||||
export const getAnimeBySlug = async (slug: string) => {
|
export interface GetAnimeBySlugResponse {
|
||||||
|
success: boolean;
|
||||||
|
status: number;
|
||||||
|
message: string;
|
||||||
|
data: {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
titleAlternative: {
|
||||||
|
type: string;
|
||||||
|
title: string;
|
||||||
|
}[];
|
||||||
|
genres: {
|
||||||
|
slug: string;
|
||||||
|
name: string;
|
||||||
|
}[];
|
||||||
|
slug: string;
|
||||||
|
malId: number;
|
||||||
|
pictureMedium: string;
|
||||||
|
pictureLarge: string;
|
||||||
|
country: string;
|
||||||
|
score: string;
|
||||||
|
status: string;
|
||||||
|
startAiring: string;
|
||||||
|
endAiring: string;
|
||||||
|
synopsis: string;
|
||||||
|
ageRating: string;
|
||||||
|
mediaType: string;
|
||||||
|
source: string;
|
||||||
|
onDraft: boolean;
|
||||||
|
uploadedBy: string;
|
||||||
|
deletedAt: null | string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getAnimeBySlug = async (
|
||||||
|
slug: string,
|
||||||
|
): Promise<GetAnimeBySlugResponse> => {
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
status: 200,
|
status: 200,
|
||||||
@ -20,6 +58,20 @@ export const getAnimeBySlug = async (slug: string) => {
|
|||||||
title: "Sakamoto Days",
|
title: "Sakamoto Days",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
genres: [
|
||||||
|
{
|
||||||
|
slug: "action",
|
||||||
|
name: "Action",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: "slice-of-life",
|
||||||
|
name: "Slice of Life",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: "comedy",
|
||||||
|
name: "Comedy",
|
||||||
|
},
|
||||||
|
],
|
||||||
slug: "sakamoto-days",
|
slug: "sakamoto-days",
|
||||||
malId: 58939,
|
malId: 58939,
|
||||||
pictureMedium: "https://myanimelist.net/images/anime/1026/146459.webp",
|
pictureMedium: "https://myanimelist.net/images/anime/1026/146459.webp",
|
||||||
|
|||||||
56
features/anime/sections/Information/main.client.tsx
Normal file
56
features/anime/sections/Information/main.client.tsx
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
"use client";
|
||||||
|
import { GetAnimeBySlugResponse } from "../../actions/getAnimeBySlug";
|
||||||
|
import GenreTags from "@/shared/components/GenreTags";
|
||||||
|
|
||||||
|
const AnimeInformationClient = ({ data }: { data: GetAnimeBySlugResponse }) => {
|
||||||
|
return (
|
||||||
|
<section className="flex gap-4">
|
||||||
|
<div>
|
||||||
|
<img
|
||||||
|
src={data.data.pictureMedium}
|
||||||
|
alt={data.data.title}
|
||||||
|
className="h-fit w-78 overflow-hidden rounded-lg"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="tracking-tight">
|
||||||
|
<div className="flex flex-col gap-0.5">
|
||||||
|
<h1 className="tracking-tighter text-5xl font-bold">
|
||||||
|
{data.data.title}
|
||||||
|
</h1>
|
||||||
|
<h3 className="ml-0.5 tracking-tight text-xl font-medium text-muted-foreground">
|
||||||
|
{data.data.titleAlternative[0].title}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div className="mt-2">
|
||||||
|
<GenreTags genres={data.data.genres} />
|
||||||
|
</div>
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr className="">
|
||||||
|
<td className="font-medium text-sm text-muted-foreground">
|
||||||
|
Score
|
||||||
|
</td>
|
||||||
|
<td className="text-sm">{data.data.score}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="font-medium text-sm text-muted-foreground">
|
||||||
|
Status
|
||||||
|
</td>
|
||||||
|
<td className="text-sm">{data.data.status}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="font-medium text-sm text-muted-foreground">
|
||||||
|
Airing
|
||||||
|
</td>
|
||||||
|
<td className="text-sm">
|
||||||
|
{data.data.startAiring} - {data.data.endAiring}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AnimeInformationClient;
|
||||||
@ -1,9 +1,14 @@
|
|||||||
import { getAnimeBySlug } from "../../actions/getAnimeBySlug";
|
import { getAnimeBySlug } from "../../actions/getAnimeBySlug";
|
||||||
|
import AnimeInformationClient from "./main.client";
|
||||||
|
|
||||||
const AnimeInformationMain = async () => {
|
const AnimeInformationMain = async () => {
|
||||||
const data = async () => await getAnimeBySlug("sakamoto-days");
|
const data = async () => await getAnimeBySlug("sakamoto-days");
|
||||||
const result = await data();
|
const result = await data();
|
||||||
return <div>Data: {JSON.stringify(result)}</div>;
|
return (
|
||||||
|
<div>
|
||||||
|
<AnimeInformationClient data={result} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AnimeInformationMain;
|
export default AnimeInformationMain;
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { Swiper, SwiperSlide } from "swiper/react";
|
|||||||
import { Icon } from "@iconify/react";
|
import { Icon } from "@iconify/react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import AddToList from "./AddToList";
|
import AddToList from "./AddToList";
|
||||||
|
import GenreTags from "@/shared/components/GenreTags";
|
||||||
|
|
||||||
export interface HeroSwiperProps {
|
export interface HeroSwiperProps {
|
||||||
data: {
|
data: {
|
||||||
@ -50,14 +51,8 @@ const HeroSwiper = (props: HeroSwiperProps) => {
|
|||||||
<h1 className="text-6xl font-semibold tracking-tight">
|
<h1 className="text-6xl font-semibold tracking-tight">
|
||||||
{slide.title}
|
{slide.title}
|
||||||
</h1>
|
</h1>
|
||||||
<div className="mt-4 flex gap-1.5">
|
<div className="mt-4">
|
||||||
{slide.genres.map((genre) => (
|
<GenreTags genres={slide.genres} />
|
||||||
<Link href={`/genres/${genre.slug}`} key={genre.slug}>
|
|
||||||
<Badge className="bg-neutral-100/60 backdrop-blur-lg text-neutral-800">
|
|
||||||
{genre.name}
|
|
||||||
</Badge>
|
|
||||||
</Link>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-4 font-medium text-base max-w-[40vw] line-clamp-6">
|
<p className="mt-4 font-medium text-base max-w-[40vw] line-clamp-6">
|
||||||
{slide.synopsis}
|
{slide.synopsis}
|
||||||
|
|||||||
25
shared/components/GenreTags.tsx
Normal file
25
shared/components/GenreTags.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
import { Badge } from "../libs/shadcn/ui/badge";
|
||||||
|
|
||||||
|
type GenreTagsProps = {
|
||||||
|
genres: {
|
||||||
|
slug: string;
|
||||||
|
name: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const GenreTags = ({ genres }: GenreTagsProps) => {
|
||||||
|
return (
|
||||||
|
<div className="flex gap-1.5">
|
||||||
|
{genres.map((genre, index) => (
|
||||||
|
<Link href={`/genres/${genre.slug}`} key={index}>
|
||||||
|
<Badge className="bg-neutral-100/60 backdrop-blur-lg text-neutral-800">
|
||||||
|
{genre.name}
|
||||||
|
</Badge>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GenreTags;
|
||||||
Reference in New Issue
Block a user