From 51dd7a159a337003c1aade2417da92b41c2e4b94 Mon Sep 17 00:00:00 2001 From: Valknar XXX Date: Tue, 28 Oct 2025 10:31:06 +0100 Subject: [PATCH] feat: implement video likes and play tracking UI Video Detail Page (/videos/[slug]): - Load like status from server on page load - Add functional like button with heart icon - Show likes count with real-time updates - Track video plays when user clicks play - Record watch progress every 10 seconds - Mark video as completed at 90% watched - Show toast notifications for like/unlike actions - Require authentication to like videos Video Listing Page (/videos): - Display play count badge on video thumbnails - Show play icon with count in top-right corner Features: - Like/unlike videos with live count updates - Play tracking with analytics data - Progress tracking for completion metrics - Authentication-gated liking functionality - User-friendly toast feedback All UI components working and integrated with backend API --- .../frontend/src/routes/videos/+page.svelte | 16 ++-- .../src/routes/videos/[slug]/+page.server.ts | 20 +++- .../src/routes/videos/[slug]/+page.svelte | 91 ++++++++++++++++--- 3 files changed, 104 insertions(+), 23 deletions(-) diff --git a/packages/frontend/src/routes/videos/+page.svelte b/packages/frontend/src/routes/videos/+page.svelte index 1e36991..e2d27ed 100644 --- a/packages/frontend/src/routes/videos/+page.svelte +++ b/packages/frontend/src/routes/videos/+page.svelte @@ -238,13 +238,15 @@ const filteredVideos = $derived(() => { {/if} - - + + {#if video.plays_count} +
+ + {video.plays_count} +
+ {/if} (null); +let lastTrackedTime = $state(0); const relatedVideos = [ { @@ -63,8 +69,30 @@ const relatedVideos = [ }, ]; -function handleLike() { - isLiked = !isLiked; +async function handleLike() { + if (!data.authStatus.authenticated) { + toast.error("Please sign in to like videos"); + return; + } + + try { + isLikeLoading = true; + if (isLiked) { + const result = await unlikeVideo(data.video.id); + likesCount = result.likes_count; + isLiked = false; + toast.success("Removed from liked videos"); + } else { + const result = await likeVideo(data.video.id); + likesCount = result.likes_count; + isLiked = true; + toast.success("Added to liked videos"); + } + } catch (error: any) { + toast.error(error.message || "Failed to update like"); + } finally { + isLikeLoading = false; + } } function handleBookmark() { @@ -90,9 +118,29 @@ async function handleComment(e: Event) { } } -let showPlayer = $state(false); +async function handlePlay() { + showPlayer = true; + try { + const result = await recordVideoPlay(data.video.id); + currentPlayId = result.play_id; + } catch (error) { + console.error("Failed to record play:", error); + } +} -const { data } = $props(); +function handleTimeUpdate(e: Event) { + const video = e.target as HTMLVideoElement; + const currentTime = Math.floor(video.currentTime); + + // Update every 10 seconds + if (currentPlayId && currentTime - lastTrackedTime >= 10) { + lastTrackedTime = currentTime; + const completed = video.currentTime >= video.duration * 0.9; // 90% watched = completed + updateVideoPlay(data.video.id, currentPlayId, currentTime, completed).catch(console.error); + } +} + +let showPlayer = $state(false); @@ -155,6 +204,21 @@ const { data } = $props(); > +
@@ -196,16 +260,17 @@ const { data } = $props();
- + + {likesCount} + - + Save -->