Skip to content

Commit

Permalink
feat: add animes page
Browse files Browse the repository at this point in the history
  • Loading branch information
mickasmt committed Mar 25, 2024
1 parent 0c519e6 commit d958dfe
Show file tree
Hide file tree
Showing 10 changed files with 364 additions and 20 deletions.
Binary file added public/images/examples/animes.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
116 changes: 116 additions & 0 deletions src/components/animes-tabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Icons } from "@/icons";
import type { Airing, Media } from "@/types";
import {
format,
formatDistanceToNowStrict,
fromUnixTime,
isPast,
} from "date-fns";

interface AnimesTabsProps {
animes: {
trending: Media[];
latestAiring: Airing[];
futureAiring: Airing[];
upcomingSeason: Media[];
};
}

interface TabsTriggerProps {
value: string;
label: string;
icon: keyof typeof Icons;
}

const tabTriggers: TabsTriggerProps[] = [
{
value: "latestAiring",
label: "In Last 24 Hours",
icon: "tv",
},
{
value: "futureAiring",
label: "Coming Up Next!",
icon: "clock",
},
{ value: "trending", label: "Trending", icon: "flame" },
{ value: "upcomingSeason", label: "Upcoming Season", icon: "calendar" },
];

export function AnimesTabs({ animes }: AnimesTabsProps) {
return (
<Tabs defaultValue="latestAiring" className="w-full">
<p className="text-sm text-muted-foreground">
<i>Images aren't optimized.</i>
</p>
<TabsList className="!bg-muted/80 mt-2 mb-4">
{tabTriggers.map((tab) => {
const Icon = Icons[tab.icon || "flame"];
return (
<TabsTrigger key={tab.value} value={tab.value}>
<div className="flex gap-x-2 items-center">
<Icon className="size-5" />
<span className="hidden sm:inline-flex">{tab.label}</span>
</div>
</TabsTrigger>
);
})}
</TabsList>

{Object.entries(animes).map(([key, value]) => (
<TabsContent key={key} value={key}>
<div className="anime-grid">
{value.map((anime, i) => (
<AnimeCard key={key + "-" + i} data={anime} />
))}
</div>
</TabsContent>
))}
</Tabs>
);
}

function AnimeCard({ data }: { data: Media | Airing }) {
let media, airingAt, episode;

if ("media" in data) {
media = data.media;
airingAt = data.airingAt;
episode = data.episode;
} else {
media = data;
}

return (
<div className="flex flex-col w-full">
<div className="w-full h-[280px] rounded-md overflow-hidden bg-muted-foreground/15">
<img
width={260}
height={300}
loading="eager"
className="size-full object-cover object-center"
src={media.coverImage.extraLarge}
alt={media.title.userPreferred}
/>
</div>

<div className="flex w-full flex-col justify-center">
<p className="font-medium text-sm text-balance line-clamp-1 mt-1 text-foreground">
{media.title.userPreferred}
</p>

{airingAt && episode ? (
<p className="flex text-xs sm:text-[13px] font-medium text-muted-foreground line-clamp-1">
{isPast(fromUnixTime(airingAt))
? formatDistanceToNowStrict(fromUnixTime(airingAt), {
addSuffix: true,
})
: format(fromUnixTime(airingAt), "p")}
&nbsp; • &nbsp;Ep. {episode}
</p>
) : null}
</div>
</div>
);
}
17 changes: 8 additions & 9 deletions src/config/nav-menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ export const navMenuConfig: NavMenuConfig = {
"A waitlist form using Astro DB, React Hook Form & Sonner. Static page.",
image: "/images/examples/waitlist.jpg",
forceReload: true,
launched: true,
},
{
title: "Pricing",
Expand All @@ -44,7 +43,6 @@ export const navMenuConfig: NavMenuConfig = {
description:
"A newsletter form using Astro DB & Simple Stack Form. Counter display!",
image: "/images/examples/newsletter.jpg",
launched: true,
},
],
},
Expand All @@ -66,6 +64,14 @@ export const navMenuConfig: NavMenuConfig = {
"A Markdown/MDX docs site built using Content Collections.",
image: "/images/examples/documentation.jpg",
},
{
title: "Anime List",
href: "/animes",
description:
"Fetch anime content from an graphql endpoint. Tabs component.",
image: "/images/examples/animes.jpg",
launched: true,
},
{
title: "Blog DB",
href: "/blog-db",
Expand All @@ -81,13 +87,6 @@ export const navMenuConfig: NavMenuConfig = {
description: "Ecommerce pages fetching data from an API.",
// image: "/images/examples/ecommerce.jpg",
},
{
title: "Anime List",
href: "/animes",
description: "Fetch anime content from an graphql endpoint",
// image: "/images/examples/animes.jpg",
disabled: true,
},
{
title: "Authentification",
href: "/auth",
Expand Down
21 changes: 17 additions & 4 deletions src/icons/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
type IconProps = React.HTMLAttributes<SVGElement>;
import {
Calendar,
Clock,
Flame,
Tv2,
type LucideIcon,
type LucideProps,
} from "lucide-react";

export type Icon = LucideIcon;

export const Icons = {
logo: (props: IconProps) => (
flame: Flame,
calendar: Calendar,
tv: Tv2,
clock: Clock,
logo: ({ ...props }: LucideProps) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
Expand All @@ -17,7 +30,7 @@ export const Icons = {
/>
</svg>
),
hamburger: (props: IconProps) => (
hamburger: ({ ...props }: LucideProps) => (
<svg
stroke="currentColor"
fill="currentColor"
Expand All @@ -39,7 +52,7 @@ export const Icons = {
></path>
</svg>
),
wrenchSrewdriver: (props: IconProps) => (
wrenchSrewdriver: ({ ...props }: LucideProps) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
Expand Down
132 changes: 132 additions & 0 deletions src/lib/graphql.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { getSeasonInfo } from "@/lib/utils";
import type { Airing, Media } from "@/types";
import { addDays, getUnixTime, subDays } from "date-fns";

const graphqlAPI = "https://graphql.anilist.co/";

export const getPlanningAnimes = async () => {
const { season, seasonYear, nextSeason, nextYear } = await getSeasonInfo();
const query = `
query getMedias(
$season: MediaSeason
$seasonYear: Int
$nextSeason: MediaSeason
$nextYear: Int
$latestAiringStart: Int
$latestAiringEnd: Int
$futureAiringStart: Int
$futureAiringEnd: Int
) {
latestAiring: Page(page: 1, perPage: 50) {
# sort : TIME_DESC
airingSchedules(
airingAt_greater: $latestAiringStart
airingAt_lesser: $latestAiringEnd
sort: TIME_DESC
) {
id
episode
airingAt
media {
...mediaFragment
}
}
}
futureAiring: Page(page: 1, perPage: 50) {
# sort : TIME
airingSchedules(
airingAt_greater: $futureAiringStart
airingAt_lesser: $futureAiringEnd
sort: TIME
) {
id
episode
airingAt
media {
...mediaFragment
}
}
}
trending: Page(page: 1, perPage: 30) {
media(sort: TRENDING_DESC, type: ANIME, isAdult: false) {
...mediaFragment
}
}
season: Page(page: 1, perPage: 30) {
media(
season: $season
seasonYear: $seasonYear
sort: POPULARITY_DESC
type: ANIME
isAdult: false
) {
...mediaFragment
}
}
nextSeason: Page(page: 1, perPage: 30) {
media(
season: $nextSeason
seasonYear: $nextYear
sort: POPULARITY_DESC
type: ANIME
isAdult: false
) {
...mediaFragment
}
}
}
fragment mediaFragment on Media {
id
title {
userPreferred
}
coverImage {
extraLarge
large
}
isAdult
}
`;

const response = await fetch(graphqlAPI, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify({
query: query,
variables: {
season: season,
seasonYear: seasonYear,
nextSeason: nextSeason,
nextYear: nextYear,
latestAiringStart: getUnixTime(subDays(new Date(), 1)),
latestAiringEnd: getUnixTime(new Date()),
futureAiringStart: getUnixTime(new Date()),
futureAiringEnd: getUnixTime(addDays(new Date(), 1)),
},
}),
});

const { data } = await response.json();

const trending: Media[] = data.trending.media;
const upcomingSeason: Media[] = data.nextSeason.media;

const latestAiring: Airing[] = data?.latestAiring?.airingSchedules.filter(
(airing: Airing) => airing.media.isAdult == false
);

const futureAiring: Airing[] = data?.futureAiring?.airingSchedules.filter(
(airing: Airing) => airing.media.isAdult == false
);

return {
trending,
upcomingSeason,
latestAiring,
futureAiring,
};
};
49 changes: 48 additions & 1 deletion src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,51 @@ export function extractSegmentURL(path: string) {

export function capitalizer(text: string) {
return text.charAt(0).toUpperCase() + text.slice(1);
}
}

export async function getSeasonInfo() {
const currentDate = new Date();
const currentMonth = currentDate.getMonth() + 1;

let season = "";
let seasonYear = currentDate.getFullYear();
let nextSeason = "";
let nextYear = seasonYear;

switch (currentMonth) {
case 12:
case 1:
case 2:
season = "WINTER";
nextSeason = "SPRING";
break;
case 3:
case 4:
case 5:
season = "SPRING";
nextSeason = "SUMMER";
break;
case 6:
case 7:
case 8:
season = "SUMMER";
nextSeason = "FALL";
break;
case 9:
case 10:
case 11:
season = "FALL";
nextSeason = "WINTER";
if (currentMonth === 11) {
nextYear++;
}
break;
}

return {
season,
seasonYear,
nextSeason,
nextYear,
};
}
Loading

0 comments on commit d958dfe

Please sign in to comment.