A self hosted solution for privately rating and reviewing different sorts of media
1import { ExtendedUserEntry } from '@/app/(app)/dashboard/state';
2import { cn } from '@/lib/utils';
3import { Category } from '@prisma/client';
4import { Book, Film, Star, Tv } from 'lucide-react';
5import { HTMLProps, ReactNode } from 'react';
6import SmallRating from './smallRating';
7import { getUserTitleFromEntry } from '@/server/api/routers/dashboard_';
8import { Badge } from './ui/badge';
9
10const UserEntryCard = ({
11 entryTitle,
12 backgroundImage,
13 category,
14 releaseDate,
15 rating,
16 className,
17 customStars,
18 hoverCard,
19 topRight,
20 ...props
21}: {
22 entryTitle: ReactNode;
23 backgroundImage: string;
24 category: Category;
25 releaseDate: Date;
26 rating: number;
27 customStars?: ReactNode;
28 hoverCard?: ReactNode;
29 topRight?: ReactNode;
30} & HTMLProps<HTMLDivElement>) => {
31 // return <img src={backgroundImage} className="aspect-[2/3] w-full"></img>;
32
33 return (
34 <div
35 className={cn(
36 'group relative z-50 aspect-[2/3] cursor-pointer overflow-clip rounded-lg bg-cover shadow-sm shadow-base-400 transition-all duration-200 hover:-translate-y-[4px] hover:scale-[101%] hover:shadow-md active:brightness-[0.8]',
37 className
38 )}
39 {...props}
40 >
41 <img src={backgroundImage} className="absolute top-0 h-full w-full" />
42 <div className="absolute top-0 flex h-[20%] w-full flex-col justify-end rounded-bl-lg rounded-br-lg bg-opacity-50 bg-gradient-to-b from-base-900 to-transparent"></div>
43 <div className="absolute top-0 p-2">
44 {(() => {
45 switch (category) {
46 case 'Book':
47 return (
48 <Badge className="flex gap-1 border border-amber-300 bg-amber-700/50 px-1 text-xs text-amber-300">
49 <Book className="size-3 stroke-amber-300" />
50 Book
51 </Badge>
52 );
53 case 'Movie':
54 return (
55 <Badge className="flex gap-1 border border-blue-300 bg-blue-700/50 px-1 text-xs text-blue-300">
56 <Book className="size-3 stroke-blue-300" />
57 Movie
58 </Badge>
59 );
60 case 'Series':
61 return (
62 <Badge className="flex gap-1 border border-green-300 bg-green-700/50 px-1 text-xs text-green-300">
63 <Book className="size-3 stroke-green-300" />
64 Tv
65 </Badge>
66 );
67 }
68 })()}
69 </div>
70 {hoverCard && (
71 <div className="duration-400 absolute right-1 top-1 z-10 -translate-y-4 rounded-sm border border-base-100 bg-white opacity-0 shadow-sm transition-all hover:bg-base-50 hover:text-base-900 group-hover:translate-y-0 group-hover:opacity-100">
72 {hoverCard}
73 </div>
74 )}
75 <div className="absolute right-0 top-0 flex p-2">{topRight}</div>
76
77 <div className="select-none text-transparent">{entryTitle}</div>
78 <div className="absolute top-[40%] hidden h-[60%] w-full flex-col justify-end rounded-bl-lg rounded-br-lg bg-gradient-to-t from-base-900 to-transparent object-cover p-2 opacity-0 transition-all duration-200 group-hover:opacity-100 md:flex">
79 <div className="text-left text-sm font-semibold text-white sm:text-base">
80 {entryTitle}
81 </div>
82 <div className="flex flex-row items-center justify-between">
83 <div className="text-sm text-base-400">
84 {isNaN(releaseDate.getFullYear()) ? '' : releaseDate.getFullYear()}
85 </div>
86 {!!customStars && customStars}
87 {!customStars && (
88 <>
89 <div className="hidden text-white sm:block">
90 <SmallRating rating={rating} />
91 </div>
92 <div className="flex items-center gap-1 text-white sm:hidden">
93 <span className="text-sm">{(rating / 20).toFixed(1)}</span>
94 <Star strokeWidth={0} className="size-4 fill-primary" />
95 </div>
96 </>
97 )}
98 </div>
99 </div>
100 </div>
101 );
102};
103
104export const UserEntryCardObject = ({
105 userEntry,
106 ...props
107}: {
108 userEntry: ExtendedUserEntry;
109} & HTMLProps<HTMLDivElement>) => {
110 return (
111 <UserEntryCard
112 {...{
113 entryTitle: getUserTitleFromEntry(userEntry.entry),
114 backgroundImage: userEntry.entry.posterPath,
115 releaseDate: userEntry.entry.releaseDate,
116 category: userEntry.entry.category,
117 rating: userEntry.rating,
118 }}
119 {...props}
120 />
121 );
122};
123
124export default UserEntryCard;