Files
lensflix/components/cards/SkipGuide.tsx
T
2026-06-10 18:30:46 +02:00

124 lines
4.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { skipGuideSchema, type SkipGuide } from "@/lib/schemas";
import { cn } from "@/lib/utils";
const recommendationStyles = {
recommended: "bg-red-400/15 text-red-200 border-red-400/25",
optional: "bg-amber-400/15 text-amber-200 border-amber-400/25",
do_not_skip: "bg-slate-400/15 text-slate-200 border-slate-400/25",
};
const recommendationLabels = {
recommended: "Recommended skip",
optional: "Optional skip",
do_not_skip: "Do not skip",
};
function parseTimestamp(value: string): number {
const hours = value.match(/(\d+)\s*h/i);
const minutes = value.match(/(\d+)\s*m/i);
return (hours ? Number(hours[1]) * 60 : 0) + (minutes ? Number(minutes[1]) : 0);
}
export function SkipGuideView({ data }: { data: unknown }) {
const result = skipGuideSchema.safeParse(data);
if (!result.success) {
return (
<div className="rounded-2xl border border-white/10 bg-white/[0.03] p-6 text-sm text-slate-400">
Could not display this insight (data format issue). Try another card or refresh later.
</div>
);
}
const parsed = result.data as SkipGuide;
const maxMinutes = Math.max(
...parsed.segments.map((segment) => parseTimestamp(segment.end)),
120,
);
return (
<div className="space-y-5 rounded-2xl border border-white/10 bg-white/[0.03] p-6">
<div className="grid gap-4 sm:grid-cols-3">
<Stat label="Original rating" value={parsed.originalRating} />
<Stat
label="Suggested age after skips"
value={`${parsed.suggestedAudienceAge}+`}
/>
<Stat
label="Skippable runtime"
value={`${parsed.totalSkippedMinutes} min`}
/>
</div>
<div>
<p className="mb-2 text-sm text-slate-400">Content timeline</p>
<div className="relative h-4 overflow-hidden rounded-full bg-emerald-400/20">
{parsed.segments.map((segment, index) => {
const start = parseTimestamp(segment.start);
const end = parseTimestamp(segment.end);
const left = (start / maxMinutes) * 100;
const width = Math.max(((end - start) / maxMinutes) * 100, 1.5);
return (
<div
key={`${segment.start}-${index}`}
className={cn(
"absolute top-0 h-full rounded-full",
segment.skipRecommendation === "do_not_skip"
? "bg-slate-500/70"
: "bg-red-500/80",
)}
style={{ left: `${left}%`, width: `${width}%` }}
title={`${segment.start} ${segment.end}`}
/>
);
})}
</div>
<div className="mt-2 flex justify-between text-xs text-slate-500">
<span>0:00</span>
<span>{Math.floor(maxMinutes / 60)}h {maxMinutes % 60}m</span>
</div>
</div>
<div className="space-y-3">
{parsed.segments.map((segment, index) => (
<div
key={`${segment.start}-${segment.end}-${index}`}
className="rounded-xl border border-white/10 bg-slate-950/40 p-4"
>
<div className="mb-2 flex flex-wrap items-center gap-2">
<span className="font-medium text-white">
{segment.start} {segment.end}
</span>
<span
className={cn(
"rounded-full border px-2 py-0.5 text-xs",
recommendationStyles[segment.skipRecommendation],
)}
>
{recommendationLabels[segment.skipRecommendation]}
</span>
<span className="text-xs capitalize text-slate-500">
Plot impact: {segment.plotImpact.replace("_", " ")}
</span>
</div>
<p className="text-sm text-slate-300">{segment.reason}</p>
<p className="mt-2 text-sm text-slate-400">
<span className="text-slate-500">Resume hint:</span>{" "}
{segment.resumeHint}
</p>
</div>
))}
</div>
<p className="text-xs text-slate-500">{parsed.disclaimer}</p>
</div>
);
}
function Stat({ label, value }: { label: string; value: string }) {
return (
<div className="rounded-xl border border-white/10 bg-slate-950/40 p-4">
<p className="text-xs uppercase tracking-wide text-slate-500">{label}</p>
<p className="mt-1 text-lg font-medium text-white">{value}</p>
</div>
);
}