104 lines
2.8 KiB
TypeScript
104 lines
2.8 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import type { CardType } from "@/lib/cards";
|
|
import { InternationalRatingsView } from "./InternationalRatings";
|
|
import { RatingExplainedView } from "./RatingExplained";
|
|
|
|
interface CardContentProps {
|
|
movieId: number;
|
|
cardType: CardType;
|
|
}
|
|
|
|
export function CardContent({ movieId, cardType }: CardContentProps) {
|
|
const [data, setData] = useState<unknown>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [retryToken, setRetryToken] = useState(0);
|
|
|
|
useEffect(() => {
|
|
const controller = new AbortController();
|
|
let cancelled = false;
|
|
|
|
async function fetchCard() {
|
|
try {
|
|
const response = await fetch(
|
|
`/api/cards/${cardType}?movieId=${movieId}`,
|
|
{ signal: controller.signal },
|
|
);
|
|
const payload = await response.json();
|
|
|
|
if (!response.ok) {
|
|
throw new Error(payload.error ?? "Something went wrong. Try again.");
|
|
}
|
|
|
|
if (!cancelled) {
|
|
setData(payload.data);
|
|
setError(null);
|
|
}
|
|
} catch (err) {
|
|
if ((err as Error).name === "AbortError" || cancelled) {
|
|
return;
|
|
}
|
|
setError(
|
|
err instanceof Error ? err.message : "Something went wrong. Try again.",
|
|
);
|
|
setData(null);
|
|
} finally {
|
|
if (!cancelled) {
|
|
setLoading(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
void fetchCard();
|
|
|
|
return () => {
|
|
cancelled = true;
|
|
controller.abort();
|
|
};
|
|
}, [cardType, movieId, retryToken]);
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="space-y-3 rounded-2xl border border-stone-200 bg-white p-6">
|
|
<div className="h-4 w-40 animate-pulse rounded bg-stone-200" />
|
|
<div className="h-4 w-full animate-pulse rounded bg-stone-200" />
|
|
<div className="h-4 w-5/6 animate-pulse rounded bg-stone-200" />
|
|
<p className="text-sm text-stone-600">Generating insight...</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<div className="rounded-2xl border border-red-200 bg-red-50 p-6">
|
|
<p className="text-red-700">{error}</p>
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
setLoading(true);
|
|
setRetryToken((token) => token + 1);
|
|
}}
|
|
className="mt-4 rounded-lg border border-stone-300 px-4 py-2 text-sm text-stone-800 transition hover:bg-stone-100"
|
|
>
|
|
Try again
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!data) {
|
|
return null;
|
|
}
|
|
|
|
switch (cardType) {
|
|
case "rating_explained":
|
|
return <RatingExplainedView data={data} />;
|
|
case "international_ratings":
|
|
return <InternationalRatingsView data={data} />;
|
|
default:
|
|
return null;
|
|
}
|
|
}
|