From 0f24bcfe6c07e2054e8f8833e7cff79b1d71fef9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20=C4=B0pek?= Date: Sun, 22 Feb 2026 09:55:08 +0300 Subject: [PATCH] feat: Implement direct video file uploads via Bunny.net and TUS protocol, adding a new API route and UI for file selection. --- .../videos/[videoId]/compare/page.tsx | 123 ++++- .../projects/[projectId]/videos/new/page.tsx | 270 ++++++++-- .../[projectId]/videos/bunny-init/route.ts | 100 ++++ bun.lock | 44 +- components/ui/tabs.tsx | 90 ++++ components/video-card.tsx | 42 +- components/video-page-content.tsx | 465 ++++++++++++++---- lib/video-providers/bunny.ts | 71 +++ lib/video-providers/index.ts | 22 +- lib/video-providers/types.ts | 8 +- next.config.ts | 2 + package.json | 2 + 12 files changed, 1054 insertions(+), 185 deletions(-) create mode 100644 app/api/projects/[projectId]/videos/bunny-init/route.ts create mode 100644 components/ui/tabs.tsx create mode 100644 lib/video-providers/bunny.ts diff --git a/app/(dashboard)/projects/[projectId]/videos/[videoId]/compare/page.tsx b/app/(dashboard)/projects/[projectId]/videos/[videoId]/compare/page.tsx index 1afd506..75ab95e 100644 --- a/app/(dashboard)/projects/[projectId]/videos/[videoId]/compare/page.tsx +++ b/app/(dashboard)/projects/[projectId]/videos/[videoId]/compare/page.tsx @@ -43,6 +43,20 @@ interface Version { _count: { comments: number }; } +interface PlayerAdapter { + playVideo: () => void; + pauseVideo: () => void; + seekTo: (time: number, allowSeekAhead?: boolean) => void; + mute: () => void; + unMute: () => void; + isMuted: () => boolean; + getCurrentTime: () => number; + getDuration: () => number; + getPlayerState: () => number; + setPlaybackRate?: (rate: number) => void; + destroy: () => void; +} + interface Comment { id: string; content: string | null; @@ -96,8 +110,8 @@ export default function CompareVersionsPage() { const [isDragging, setIsDragging] = useState(false); const timelineRef = useRef(null); - // Map of versionId -> YT.Player - const playersRef = useRef>(new Map()); + // Map of versionId -> YT.Player or Custom Adapter + const playersRef = useRef>(new Map()); const rafRef = useRef(null); // Comments state per panel @@ -219,7 +233,7 @@ export default function CompareVersionsPage() { }, [isDragging]); // Register/unregister players - const registerPlayer = useCallback((versionId: string, player: YT.Player) => { + const registerPlayer = useCallback((versionId: string, player: YT.Player | PlayerAdapter) => { playersRef.current.set(versionId, player); }, []); @@ -319,7 +333,7 @@ export default function CompareVersionsPage() { e.preventDefault(); players.forEach((p) => { try { - if (p.isMuted()) { p.unMute(); } else { p.mute(); } + if (p.isMuted?.()) { p.unMute?.(); } else { p.mute?.(); } } catch { /* */ } }); break; @@ -552,6 +566,13 @@ export default function CompareVersionsPage() { onRegister={registerPlayer} onUnregister={unregisterPlayer} /> + ) : version.providerId === 'bunny' ? ( + ) : (