@@ -31,71 +31,119 @@ const href = isEpisode
3131 : ` /media/movie/${media .tmdb_id } ` ;
3232---
3333
34- <a href ={ href } class =" flex gap-5 p-4 bg-zinc-900/40 hover:bg-zinc-800/60 border border-zinc-800/50 hover:border-blue-500/30 rounded-2xl transition-all duration-300 group shadow-sm hover:shadow-xl hover:shadow-blue-900/10 active:opacity-70" >
35- <!-- Poster -->
36- <div class =" w-20 shrink-0 aspect-[2/3] rounded-xl overflow-hidden bg-zinc-950 shadow-2xl ring-1 ring-white/5 group-hover:ring-blue-500/30 transition-all duration-300" >
37- { posterPath ? (
38- <img
39- src = { posterPath }
40- alt = { title }
41- class = " w-full h-full object-cover group-hover:scale-110 transition-transform duration-500"
42- loading = " lazy"
43- decoding = " async"
44- />
45- ) : (
46- <div class = " w-full h-full flex flex-col items-center justify-center text-xs text-zinc-700 p-2 text-center bg-zinc-900" >
47- <span class = " text-xl mb-1 opacity-20" >🎬</span >
48- <span class = " font-bold uppercase tracking-tighter truncate w-full" >{ title } </span >
49- </div >
50- )}
51- </div >
52-
53- <!-- Content -->
54- <div class =" flex flex-col justify-center min-w-0 flex-1 py-1" >
55- <div class =" flex items-start justify-between gap-4" >
56- <div class =" min-w-0 flex-1" >
57- <p class =" text-lg font-black text-zinc-100 group-hover:text-blue-400 transition-colors truncate tracking-tight leading-tight mb-1" >
58- { title }
59- </p >
60-
61- { isEpisode ? (
62- <p class = " text-xs font-bold text-zinc-400 flex items-center gap-2" >
63- <span class = " text-blue-500 tracking-tighter uppercase bg-blue-500/10 px-1.5 py-0.5 rounded border border-blue-500/20" >
64- S{ media .season_number ?.toString ().padStart (2 , ' 0' )} E{ media .episode_number ?.toString ().padStart (2 , ' 0' )}
65- </span >
66- <span class = " truncate font-medium text-zinc-500" >{ media .title } </span >
67- </p >
68- ) : (
69- <p class = " text-xs font-bold text-zinc-500 uppercase tracking-widest" >
70- { year }
71- </p >
72- )}
73- </div >
74-
75- <div class =" text-xs font-bold text-zinc-400 bg-zinc-950/50 border border-zinc-800 px-2 py-1 rounded-lg shrink-0 uppercase tracking-wider" >
76- { timeString }
77- </div >
34+ <div class =" relative group/card" data-event-id ={ event .id } >
35+ <a href ={ href } class =" flex gap-5 p-4 bg-zinc-900/40 hover:bg-zinc-800/60 border border-zinc-800/50 hover:border-blue-500/30 rounded-2xl transition-all duration-300 group shadow-sm hover:shadow-xl hover:shadow-blue-900/10 active:opacity-70" >
36+ <!-- Poster -->
37+ <div class =" w-20 shrink-0 aspect-[2/3] rounded-xl overflow-hidden bg-zinc-950 shadow-2xl ring-1 ring-white/5 group-hover:ring-blue-500/30 transition-all duration-300" >
38+ { posterPath ? (
39+ <img
40+ src = { posterPath }
41+ alt = { title }
42+ class = " w-full h-full object-cover group-hover:scale-110 transition-transform duration-500"
43+ loading = " lazy"
44+ decoding = " async"
45+ />
46+ ) : (
47+ <div class = " w-full h-full flex flex-col items-center justify-center text-xs text-zinc-700 p-2 text-center bg-zinc-900" >
48+ <span class = " text-xl mb-1 opacity-20" >🎬</span >
49+ <span class = " font-bold uppercase tracking-tighter truncate w-full" >{ title } </span >
50+ </div >
51+ )}
7852 </div >
79-
80- <!-- Progress bar if not completed -->
81- { ! event .completed && event .progress_percent > 0 ? (
82- <div class = " mt-4" >
83- <div class = " flex items-center justify-between text-xs font-bold text-zinc-400 uppercase tracking-widest mb-1.5" >
84- <span >Watching</span >
85- <span >{ Math .round (event .progress_percent * 100 )} %</span >
53+
54+ <!-- Content -->
55+ <div class =" flex flex-col justify-center min-w-0 flex-1 py-1" >
56+ <div class =" flex items-start justify-between gap-4" >
57+ <div class =" min-w-0 flex-1" >
58+ <p class =" text-lg font-black text-zinc-100 group-hover:text-blue-400 transition-colors truncate tracking-tight leading-tight mb-1" >
59+ { title }
60+ </p >
61+
62+ { isEpisode ? (
63+ <p class = " text-xs font-bold text-zinc-400 flex items-center gap-2" >
64+ <span class = " text-blue-500 tracking-tighter uppercase bg-blue-500/10 px-1.5 py-0.5 rounded border border-blue-500/20" >
65+ S{ media .season_number ?.toString ().padStart (2 , ' 0' )} E{ media .episode_number ?.toString ().padStart (2 , ' 0' )}
66+ </span >
67+ <span class = " truncate font-medium text-zinc-500" >{ media .title } </span >
68+ </p >
69+ ) : (
70+ <p class = " text-xs font-bold text-zinc-500 uppercase tracking-widest" >
71+ { year }
72+ </p >
73+ )}
8674 </div >
87- <div class = " w-full bg-zinc-950 h-1.5 rounded-full overflow-hidden border border-zinc-800/50" >
88- <div
89- class = " bg-blue-600 h-full rounded-full shadow-[0_0_8px_rgba(37,99,235,0.4)]"
90- style = { ` width: ${event .progress_percent * 100 }% ` }
91- ></div >
75+
76+ <div class =" text-xs font-bold text-zinc-400 bg-zinc-950/50 border border-zinc-800 px-2 py-1 rounded-lg shrink-0 uppercase tracking-wider" >
77+ { timeString }
9278 </div >
9379 </div >
94- ) : (
95- <div class = " mt-4 flex items-center gap-1.5" >
96- <span class = " w-1.5 h-1.5 bg-green-500 rounded-full shadow-[0_0_8px_rgba(34,197,94,0.4)]" ></span >
97- <span class = " text-xs font-bold text-zinc-400 uppercase tracking-widest" >Completed</span >
98- </div >
99- )}
100- </div >
101- </a >
80+
81+ <!-- Progress bar if not completed -->
82+ { ! event .completed && event .progress_percent > 0 ? (
83+ <div class = " mt-4" >
84+ <div class = " flex items-center justify-between text-xs font-bold text-zinc-400 uppercase tracking-widest mb-1.5" >
85+ <span >Watching</span >
86+ <span >{ Math .round (event .progress_percent * 100 )} %</span >
87+ </div >
88+ <div class = " w-full bg-zinc-950 h-1.5 rounded-full overflow-hidden border border-zinc-800/50" >
89+ <div
90+ class = " bg-blue-600 h-full rounded-full shadow-[0_0_8px_rgba(37,99,235,0.4)]"
91+ style = { ` width: ${event .progress_percent * 100 }% ` }
92+ ></div >
93+ </div >
94+ </div >
95+ ) : (
96+ <div class = " mt-4 flex items-center gap-1.5" >
97+ <span class = " w-1.5 h-1.5 bg-green-500 rounded-full shadow-[0_0_8px_rgba(34,197,94,0.4)]" ></span >
98+ <span class = " text-xs font-bold text-zinc-400 uppercase tracking-widest" >Completed</span >
99+ </div >
100+ )}
101+ </div >
102+ </a >
103+
104+ <!-- Delete button — visible on card hover -->
105+ <button
106+ class =" delete-history-event absolute bottom-2 right-2 w-7 h-7 rounded-lg flex items-center justify-center bg-zinc-900/80 border border-zinc-700/50 text-zinc-500 hover:text-red-400 hover:border-red-500/40 hover:bg-red-500/10 transition-all duration-200 cursor-pointer"
107+ title =" Delete this entry"
108+ aria-label =" Delete history entry"
109+ >
110+ <svg xmlns =" http://www.w3.org/2000/svg" width =" 13" height =" 13" viewBox =" 0 0 24 24" fill =" none" stroke =" currentColor" stroke-width =" 2.5" stroke-linecap =" round" stroke-linejoin =" round" >
111+ <polyline points =" 3 6 5 6 21 6" /><path d =" M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6" /><path d =" M10 11v6" /><path d =" M14 11v6" /><path d =" M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2" />
112+ </svg >
113+ </button >
114+ </div >
115+
116+ <script >
117+ document.querySelectorAll<HTMLElement>('[data-event-id]').forEach(card => {
118+ const btn = card.querySelector<HTMLButtonElement>('.delete-history-event');
119+ if (!btn) return;
120+
121+ btn.addEventListener('click', async (e) => {
122+ e.preventDefault();
123+ e.stopPropagation();
124+
125+ const eventId = card.dataset.eventId;
126+ const token = (window as any).__AUTH_TOKEN__ ?? '';
127+
128+ btn.disabled = true;
129+ btn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" class="animate-spin"><circle cx="12" cy="12" r="10" stroke-opacity="0.25"/><path d =" M12 2a10 10 0 0 1 10 10" /></svg >`;
130+
131+ try {
132+ const res = await fetch (` /api/proxy/history/event/${eventId } ` , {
133+ method: ' DELETE' ,
134+ headers: { ' Authorization' : ` Bearer ${token } ` },
135+ });
136+ if (! res .ok ) throw new Error (' Failed' );
137+ card .style .transition = ' opacity 0.25s, transform 0.25s' ;
138+ card .style .opacity = ' 0' ;
139+ card .style .transform = ' scale(0.97)' ;
140+ setTimeout (() => card .remove (), 250 );
141+ } catch {
142+ btn .disabled = false ;
143+ btn .innerHTML = ` <svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/></svg> ` ;
144+ btn .classList .add (' text-red-400' , ' border-red-500/40' );
145+ setTimeout (() => btn .classList .remove (' text-red-400' , ' border-red-500/40' ), 1500 );
146+ }
147+ });
148+ });
149+ </script >
0 commit comments