Skip to main content
Glama
movie-card.tsx6.19 kB
import { CalendarDays, Clock, ExternalLink, Languages, Sparkles, Star, } from "lucide-react"; import React from "react"; import { Card, CardContent, CardDescription, CardTitle, } from "@/components/ui/card"; import { cn } from "@/lib/utils"; export type MovieCardProps = { title: string; posterUrl?: string; releaseDate?: string | Date; description?: string; cast?: string[]; runtimeMinutes?: number; rating?: number; genres?: string[]; language?: string; tagline?: string; studio?: string; query?: string; className?: string; onOpenShowtimes?: (url: string) => void; }; type MetaChipProps = { icon: React.ReactNode; label: string; }; function MetaChip({ icon, label }: MetaChipProps) { return ( <span className="bg-muted text-foreground/80 inline-flex items-center gap-1 rounded-full px-2.5 py-1 text-[11px] font-medium leading-none shadow-[0_0_0_1px_var(--border)]"> {icon} <span className="truncate">{label}</span> </span> ); } function formatRuntime(runtimeMinutes?: number) { if (!runtimeMinutes) return null; const hours = Math.floor(runtimeMinutes / 60); const minutes = runtimeMinutes % 60; if (hours === 0) return `${minutes}m`; return `${hours}h ${minutes.toString().padStart(2, "0")}m`; } function formatDate(releaseDate?: string | Date) { if (!releaseDate) return null; const date = new Date(releaseDate); if (Number.isNaN(date.getTime())) return null; return new Intl.DateTimeFormat("en", { month: "short", day: "numeric", year: "numeric", }).format(date); } export function MovieCard({ title, posterUrl, releaseDate, description, cast, runtimeMinutes, rating, genres, language, tagline, studio, query, className, onOpenShowtimes, }: MovieCardProps) { const formattedDate = formatDate(releaseDate); const formattedRuntime = formatRuntime(runtimeMinutes); const castLine = cast && cast.length > 0 ? `${cast.slice(0, 4).join(", ")}${cast.length > 4 ? ` +${cast.length - 4} more` : ""}` : null; return ( <Card className={cn("w-full h-screen bg-card/80", className)}> <CardContent className="relative flex h-full items-stretch gap-4 p-5 sm:gap-6 sm:p-6"> {typeof rating === "number" && ( <div className="bg-amber-50 text-amber-900 absolute right-5 top-5 z-10 flex items-center gap-1 rounded-full px-3 py-1 text-sm font-semibold shadow-[0_0_0_1px_rgba(251,191,36,0.35)] sm:right-6 sm:top-6"> <Star className="size-4 fill-amber-400 text-amber-500" /> <span>{rating.toFixed(1)}</span> </div> )} <div className="relative aspect-[2/3] w-[140px] shrink-0 overflow-hidden rounded-lg border bg-gradient-to-br from-muted to-muted/50 shadow-inner sm:w-[180px]"> {posterUrl ? ( <img src={posterUrl} alt={`${title} poster`} className="h-full w-full object-cover" loading="lazy" referrerPolicy="no-referrer" /> ) : ( <div className="flex h-full w-full items-center justify-center bg-[radial-gradient(circle_at_30%_20%,rgba(0,0,0,0.08),transparent_40%),radial-gradient(circle_at_80%_0%,rgba(0,0,0,0.06),transparent_35%),radial-gradient(circle_at_50%_60%,rgba(0,0,0,0.08),transparent_35%)] text-center text-xs font-semibold uppercase tracking-wide text-muted-foreground"> No Poster </div> )} </div> <div className="min-w-0 flex-1 space-y-3"> <div className="flex items-start gap-3"> <div className="min-w-0 space-y-1"> <CardTitle className="text-base leading-tight sm:text-lg"> {title} </CardTitle> {tagline && ( <p className="text-xs font-medium uppercase tracking-[0.08em] text-muted-foreground"> {tagline} </p> )} {studio && ( <CardDescription className="text-xs">{studio}</CardDescription> )} </div> </div> {title && onOpenShowtimes && ( <button type="button" onClick={() => { const searchQuery = `${title} showtimes near me`; const url = `https://www.google.com/search?q=${encodeURIComponent(searchQuery)}`; onOpenShowtimes(url); }} className="text-primary inline-flex items-center gap-1 text-sm font-semibold hover:underline" > See showtimes <ExternalLink className="size-3.5" aria-hidden /> </button> )} <div className="flex flex-wrap items-center gap-2"> {formattedDate && ( <MetaChip icon={<CalendarDays className="size-3.5" aria-hidden />} label={formattedDate} /> )} {formattedRuntime && ( <MetaChip icon={<Clock className="size-3.5" aria-hidden />} label={formattedRuntime} /> )} {language && ( <MetaChip icon={<Languages className="size-3.5" aria-hidden />} label={language} /> )} {genres && genres.length > 0 && ( <MetaChip icon={<Sparkles className="size-3.5" aria-hidden />} label={ genres.length > 2 ? `${genres.slice(0, 2).join(" • ")} +${genres.length - 2}` : genres.join(" • ") } /> )} </div> {description && ( <p className="text-sm leading-relaxed text-muted-foreground"> {description} </p> )} {castLine && ( <div className="text-xs leading-relaxed text-muted-foreground"> <span className="text-foreground">Cast:</span> {castLine} </div> )} </div> </CardContent> </Card> ); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/khandrew1/usher-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server