From 3a3b1e53586f63768bcd36507dfccd92378dd8c5 Mon Sep 17 00:00:00 2001 From: "[dyad]" Date: Fri, 30 Jan 2026 09:06:50 +0100 Subject: [PATCH] [dyad] Added thumbnails to the clips overview page - wrote 3 file(s), executed 1 SQL queries --- src/app/account/page.tsx | 3 ++- src/components/user-clips.tsx | 30 ++++++++++++++++++++++++++---- src/components/video-editor.tsx | 10 ++++++++++ 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/app/account/page.tsx b/src/app/account/page.tsx index fc34864..ea20953 100644 --- a/src/app/account/page.tsx +++ b/src/app/account/page.tsx @@ -13,6 +13,7 @@ export type Clip = { short_id: string; title: string; storage_path: string; + thumbnail_storage_path: string; created_at: string; }; @@ -31,7 +32,7 @@ export default function AccountPage() { const { data, error } = await supabase .from('clips') - .select('id, short_id, title, storage_path, created_at') + .select('id, short_id, title, storage_path, thumbnail_storage_path, created_at') .eq('user_id', user.id) .order('created_at', { ascending: false }); diff --git a/src/components/user-clips.tsx b/src/components/user-clips.tsx index dc866d9..2fea53b 100644 --- a/src/components/user-clips.tsx +++ b/src/components/user-clips.tsx @@ -15,6 +15,7 @@ import { formatDistanceToNow } from 'date-fns'; import { MoreVertical, Edit, Trash2, Loader2, Copy, Check } from 'lucide-react'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'; import Link from 'next/link'; +import Image from 'next/image'; type UserClipsProps = { clips: Clip[]; @@ -61,7 +62,11 @@ export function UserClips({ clips, onClipDeleted, onClipUpdated }: UserClipsProp const handleDeleteClip = async (clip: Clip) => { setIsDeleting(true); try { - const { error: storageError } = await supabase.storage.from('clips').remove([clip.storage_path]); + const pathsToRemove = [clip.storage_path]; + if (clip.thumbnail_storage_path) { + pathsToRemove.push(clip.thumbnail_storage_path); + } + const { error: storageError } = await supabase.storage.from('clips').remove(pathsToRemove); if (storageError) throw storageError; const { error: dbError } = await supabase.from('clips').delete().eq('id', clip.id); @@ -84,6 +89,11 @@ export function UserClips({ clips, onClipDeleted, onClipUpdated }: UserClipsProp setTimeout(() => setHasCopied(null), 2000); }; + const getThumbnailUrl = (path: string) => { + const { data } = supabase.storage.from('clips').getPublicUrl(path); + return data.publicUrl; + } + return ( <> @@ -120,8 +130,20 @@ export function UserClips({ clips, onClipDeleted, onClipUpdated }: UserClipsProp -
- Click to view +
+ {clip.thumbnail_storage_path ? ( + {clip.title + ) : ( +
+ No thumbnail +
+ )}
@@ -139,7 +161,7 @@ export function UserClips({ clips, onClipDeleted, onClipUpdated }: UserClipsProp Are you sure? - This action cannot be undone. This will permanently delete your clip and remove its data from our servers. + This action cannot be undone. This will permanently delete your clip and its thumbnail from our servers. diff --git a/src/components/video-editor.tsx b/src/components/video-editor.tsx index 31d6bae..ece3db8 100644 --- a/src/components/video-editor.tsx +++ b/src/components/video-editor.tsx @@ -118,16 +118,26 @@ export function VideoEditor() { const data = await ffmpeg.readFile('output.mp4'); const trimmedBlob = new Blob([(data as any).buffer], { type: 'video/mp4' }); + + // Generate thumbnail + await ffmpeg.exec(['-i', 'output.mp4', '-ss', '00:00:00.001', '-vframes', '1', 'thumbnail.jpg']); + const thumbnailData = await ffmpeg.readFile('thumbnail.jpg'); + const thumbnailBlob = new Blob([(thumbnailData as any).buffer], { type: 'image/jpeg' }); const shortId = Math.random().toString(36).substring(2, 8); const storagePath = `${user.id}/${shortId}-${videoFile.name}`; + const thumbnailStoragePath = `${user.id}/${shortId}-thumbnail.jpg`; const { error: uploadError } = await supabase.storage.from('clips').upload(storagePath, trimmedBlob); if (uploadError) throw new Error(`Storage Error: ${uploadError.message}`); + const { error: thumbnailUploadError } = await supabase.storage.from('clips').upload(thumbnailStoragePath, thumbnailBlob); + if (thumbnailUploadError) throw new Error(`Thumbnail Upload Error: ${thumbnailUploadError.message}`); + const { error: dbError } = await supabase.from('clips').insert({ user_id: user.id, storage_path: storagePath, + thumbnail_storage_path: thumbnailStoragePath, short_id: shortId, original_file_name: videoFile.name, title: videoTitle,