From 8b49a10a8c7a6b11ae62c129d2d294e8ee630343 Mon Sep 17 00:00:00 2001 From: Dylan De Faoite Date: Mon, 22 Jun 2026 22:50:07 +0100 Subject: [PATCH] refactor(frontend): remove dead code --- frontend/src/components/Box.tsx | 22 ++- frontend/src/components/Dropdown.tsx | 47 ++--- frontend/src/components/Selector.tsx | 26 ++- frontend/src/components/Sidebar.tsx | 1 - .../src/components/buttons/SidebarButton.tsx | 6 +- .../src/components/video/ClipRangeSlider.tsx | 40 ++--- frontend/src/components/video/ConfigBox.tsx | 35 ++-- frontend/src/components/video/MetadataBox.tsx | 36 ++-- .../src/components/video/PlaybackSlider.tsx | 35 ++-- frontend/src/components/video/Playbar.tsx | 49 ++---- frontend/src/components/video/VideoCard.tsx | 9 +- frontend/src/pages/Home.tsx | 6 +- frontend/src/utils/endpoints.ts | 164 +++++------------- frontend/src/utils/utils.ts | 82 +++------ 14 files changed, 185 insertions(+), 373 deletions(-) diff --git a/frontend/src/components/Box.tsx b/frontend/src/components/Box.tsx index 55536fa..31a3872 100644 --- a/frontend/src/components/Box.tsx +++ b/frontend/src/components/Box.tsx @@ -1,16 +1,14 @@ import clsx from "clsx"; -type props = { - children: React.ReactNode, - className?: string -} +type Props = { + children: React.ReactNode; + className?: string; +}; -const Box = ({children, className}: props) => { - return ( -
- { children } -
- ) -} +const Box = ({ children, className }: Props) => ( +
+ {children} +
+); -export default Box; \ No newline at end of file +export default Box; diff --git a/frontend/src/components/Dropdown.tsx b/frontend/src/components/Dropdown.tsx index 882d031..db384e0 100644 --- a/frontend/src/components/Dropdown.tsx +++ b/frontend/src/components/Dropdown.tsx @@ -1,54 +1,45 @@ -import React, {useEffect, useRef} from "react"; +import React, { useState, useEffect, useRef } from "react"; import clsx from "clsx"; type DropdownItemProps = { item: string; onClick: (item: string) => void; className?: string; -} +}; -const DropdownItem = ({ item, onClick, className }: DropdownItemProps) => { - return ( -
  • onClick(item)} - > - {item} -
  • - ); -} +const DropdownItem = ({ item, onClick, className }: DropdownItemProps) => ( +
  • onClick(item)} + > + {item} +
  • +); type DropdownProps = { label: React.ReactNode; children: React.ReactNode; className?: string; -} +}; const Dropdown = ({ label, children, className }: DropdownProps) => { - const [isOpen, setIsOpen] = React.useState(false); + const [isOpen, setIsOpen] = useState(false); const ref = useRef(null); - const toggleDropdown = () => { - setIsOpen(!isOpen); - }; - useEffect(() => { - function handleClickOutside(event: { target: any }) { - if (ref.current && !ref.current.contains(event.target)) { + const handleClickOutside = (e: MouseEvent) => { + if (ref.current && !ref.current.contains(e.target as Node)) { setIsOpen(false); } - } - - document.addEventListener('mousedown', handleClickOutside); - return () => { - document.removeEventListener('mousedown', handleClickOutside); }; + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); }, []); return ( -
    +
    - - - +
    ); -} \ No newline at end of file +} diff --git a/frontend/src/components/video/VideoCard.tsx b/frontend/src/components/video/VideoCard.tsx index 7cc2519..084aafc 100644 --- a/frontend/src/components/video/VideoCard.tsx +++ b/frontend/src/components/video/VideoCard.tsx @@ -25,9 +25,12 @@ const VideoCard = ({ id, title, duration, createdAt, onEdit, onDelete, className const [confirmDelete, setConfirmDelete] = useState(false); const menuRef = useRef(null); - setTimeout(() => { - setTimeAgo(dateToTimeAgo(stringToDate(createdAt))); - }, 1000); + useEffect(() => { + const interval = setInterval(() => { + setTimeAgo(dateToTimeAgo(stringToDate(createdAt))); + }, 1000); + return () => clearInterval(interval); + }, [createdAt]); useEffect(() => { isThumbnailAvailable(id) diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx index 73e7258..c2f704d 100644 --- a/frontend/src/pages/Home.tsx +++ b/frontend/src/pages/Home.tsx @@ -3,8 +3,8 @@ import { Upload, Scissors, SlidersHorizontal, Radio, LayoutGrid, Play } from "lu const features = [ { icon: Upload, - title: "Upload & convert", - description: "Drop in a video file. Non-native formats are converted automatically before editing.", + title: "Upload & compress", + description: "Drop in a video file, set your clip range and export settings, then compress.", }, { icon: Scissors, @@ -71,7 +71,7 @@ const Home = () => { {/* Format note */}

    - Supports MP4, MOV, WebM, and OGG. Non-native formats are converted on upload. + Supports MP4, MOV, WebM, and OGG.

    ); diff --git a/frontend/src/utils/endpoints.ts b/frontend/src/utils/endpoints.ts index b711d07..d75b09b 100644 --- a/frontend/src/utils/endpoints.ts +++ b/frontend/src/utils/endpoints.ts @@ -1,4 +1,4 @@ -import type {VideoMetadata, APIResponse, User, Clip, JobResponse } from "./types.ts"; +import type { VideoMetadata, APIResponse, User, Clip, JobResponse } from "./types.ts"; const API_URL = import.meta.env.VITE_API_URL; @@ -6,57 +6,34 @@ export class AuthError extends Error { constructor() { super("Not authenticated"); this.name = "AuthError"; } } -/** - * Login function - * @param GoogleToken - The Google token received from the frontend. - * @return A promise that resolves to a JWT - */ -const login = async (GoogleToken: string): Promise => { +const login = async (googleToken: string): Promise => { const response = await fetch(API_URL + '/api/v1/users/login', { method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ token: GoogleToken }), - credentials: 'include' + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ token: googleToken }), + credentials: 'include', }); - if (!response.ok) { - throw new Error(`Login failed: ${response.status}`); - } + if (!response.ok) throw new Error(`Login failed: ${response.status}`); const result: APIResponse = await response.json(); - - if (result.status === "error") { - throw new Error(`Login failed: ${result.message}`); - } + if (result.status === 'error') throw new Error(`Login failed: ${result.message}`); return result.data.token; -} +}; -const logout = async () => { +const logout = async (): Promise => { const response = await fetch(API_URL + '/api/v1/users/logout', { method: 'POST', - credentials: 'include' + credentials: 'include', }); - if (!response.ok) { - throw new Error(`Logout failed: ${response.status}`); - } + if (!response.ok) throw new Error(`Logout failed: ${response.status}`); const result: APIResponse = await response.json(); + if (result.status === 'error') throw new Error(`Logout failed: ${result.message}`); +}; - if (result.status === "error") { - throw new Error(`Logout failed: ${result.message}`); - } -} - -/** - * Uploads a file and starts compression in one step. - * @param file - The file to compress. - * @param options - The clip/compression options. - * @returns The job UUID to poll for progress. - */ const compress = async (file: File, options: VideoMetadata): Promise => { const formData = new FormData(); formData.append('file', file); @@ -75,114 +52,69 @@ const compress = async (file: File, options: VideoMetadata): Promise => credentials: 'include', }); - if (!response.ok) { - throw new Error(`Compression failed: ${response.status}`); - } + if (!response.ok) throw new Error(`Compression failed: ${response.status}`); const result: APIResponse = await response.json(); - - if (result.status === 'error') { - throw new Error(`Compression failed: ${result.message}`); - } + if (result.status === 'error') throw new Error(`Compression failed: ${result.message}`); return result.data.uuid; }; -/** - * Fetches the status of a background job. - * @param uuid - The job UUID. - */ const getJob = async (uuid: string): Promise => { const response = await fetch(API_URL + `/api/v1/jobs/${uuid}`, { credentials: 'include' }); - if (!response.ok) { - throw new Error(`Failed to fetch job: ${response.status}`); - } + if (!response.ok) throw new Error(`Failed to fetch job: ${response.status}`); const result: APIResponse = await response.json(); - - if (result.status === 'error') { - throw new Error(`Failed to fetch job: ${result.message}`); - } + if (result.status === 'error') throw new Error(`Failed to fetch job: ${result.message}`); return result.data; }; -/** - * Fetches the current user information. Returns null if not authenticated. - */ -const getUser = async (): Promise => { - const response = await fetch(API_URL + '/api/v1/users/me', {credentials: "include"}); +const getUser = async (): Promise => { + const response = await fetch(API_URL + '/api/v1/users/me', { credentials: 'include' }); + if (!response.ok) return null; - if (!response.ok) { - return null; - } - - const result = await response.json(); - - if (result.status === "error") { - return null; - } - - console.log(result.data); + const result: APIResponse = await response.json(); + if (result.status === 'error') return null; return result.data; -} +}; -/** - * Fetches all clips for the current user. - */ const getClips = async (): Promise => { - const response = await fetch(API_URL + '/api/v1/clips', { credentials: 'include'}); + const response = await fetch(API_URL + '/api/v1/clips', { credentials: 'include' }); if (!response.ok) { const errorResult: APIResponse = await response.json(); throw new Error(`Failed to fetch clips: ${errorResult.message}`); } - try { - const result: APIResponse = await response.json(); - return result.data; - } catch { - throw new Error('Failed to parse response'); - } -} + const result: APIResponse = await response.json(); + return result.data; +}; -/** - * Fetches a clip by its ID. - * @param id - */ const getClipById = async (id: string): Promise => { - const response = await fetch(API_URL + `/api/v1/clips/${id}`, {credentials: "include",}); + const response = await fetch(API_URL + `/api/v1/clips/${id}`, { credentials: 'include' }); if (!response.ok) { if (response.status === 401 || response.status === 403) throw new AuthError(); throw new Error(`Failed to fetch clip: ${response.status}`); } - try{ - const result: APIResponse = await response.json(); - return result.data; - } catch (error: unknown) { - throw new Error(`Failed to parse clip response: - ${error instanceof Error ? error.message : 'Unknown error'}`); - } + const result: APIResponse = await response.json(); + return result.data; }; -const getVideoBlob = async(id: string): Promise => { - const response = await fetch(API_URL + `/api/v1/clips/${id}/media`, {credentials: "include",}); +const getVideoBlob = async (id: string): Promise => { + const response = await fetch(API_URL + `/api/v1/clips/${id}/media`, { credentials: 'include' }); if (!response.ok) { if (response.status === 401 || response.status === 403) throw new AuthError(); - throw new Error(`Failed to fetch video: ${id}: ${response.status}`) + throw new Error(`Failed to fetch video ${id}: ${response.status}`); } - try { - return response.blob(); - } catch { - throw new Error(`Failed to convert Clip Return Object to blob`); - } -} + return response.blob(); +}; const patchClip = async (id: number, data: { title?: string; description?: string }): Promise => { const response = await fetch(API_URL + `/api/v1/clips/${id}`, { @@ -192,14 +124,10 @@ const patchClip = async (id: number, data: { title?: string; description?: strin body: JSON.stringify(data), }); - if (!response.ok) { - throw new Error(`Failed to update clip: ${response.status}`); - } + if (!response.ok) throw new Error(`Failed to update clip: ${response.status}`); const result: APIResponse = await response.json(); - if (result.status === 'error') { - throw new Error(`Failed to update clip: ${result.message}`); - } + if (result.status === 'error') throw new Error(`Failed to update clip: ${result.message}`); return result.data; }; @@ -210,14 +138,10 @@ const deleteClip = async (id: number): Promise => { credentials: 'include', }); - if (!response.ok) { - throw new Error(`Failed to delete clip: ${response.status}`); - } + if (!response.ok) throw new Error(`Failed to delete clip: ${response.status}`); const result: APIResponse = await response.json(); - if (result.status === 'error') { - throw new Error(`Failed to delete clip: ${result.message}`); - } + if (result.status === 'error') throw new Error(`Failed to delete clip: ${result.message}`); }; const getStreamStatus = async (): Promise => { @@ -228,14 +152,13 @@ const getStreamStatus = async (): Promise => { }; const isThumbnailAvailable = async (id: number): Promise => { - const response = await fetch(API_URL + `/api/v1/clips/${id}/thumbnail`, {credentials: "include"}); + const response = await fetch(API_URL + `/api/v1/clips/${id}/thumbnail`, { credentials: 'include' }); return response.ok; -} +}; export { login, logout, - getStreamStatus, compress, getJob, getUser, @@ -244,5 +167,6 @@ export { getVideoBlob, patchClip, deleteClip, - isThumbnailAvailable -}; \ No newline at end of file + getStreamStatus, + isThumbnailAvailable, +}; diff --git a/frontend/src/utils/utils.ts b/frontend/src/utils/utils.ts index 79dd989..de79921 100644 --- a/frontend/src/utils/utils.ts +++ b/frontend/src/utils/utils.ts @@ -2,76 +2,34 @@ function formatTime(seconds: number): string { const h = Math.floor(seconds / 3600); const m = Math.floor((seconds % 3600) / 60); const s = Math.floor(seconds % 60); - - const padded = (n: number) => n.toString().padStart(2, '0'); - - if (h > 0) { - return `${h}:${padded(m)}:${padded(s)}`; - } else { - return `${m}:${padded(s)}`; - } + const pad = (n: number) => n.toString().padStart(2, '0'); + if (h > 0) return `${h}:${pad(m)}:${pad(s)}`; + return `${m}:${pad(s)}`; } function stringToDate(dateString: string): Date { - const validIso = dateString.substring(0, 23); - const date = new Date(validIso); - if (isNaN(date.getTime())) { - throw new Error("Invalid date string"); - } + const date = new Date(dateString.substring(0, 23)); + if (isNaN(date.getTime())) throw new Error("Invalid date string"); return date; } function dateToTimeAgo(date: Date): string { - const now = new Date(); - const secondsAgo = Math.floor((now.getTime() - date.getTime()) / 1000); - - if (secondsAgo < 60) { - return `${secondsAgo} seconds ago`; - } else if (secondsAgo < 3600) { - const minutes = Math.floor(secondsAgo / 60); - - if (minutes === 1) { - return "1 minute ago"; + const secondsAgo = Math.floor((Date.now() - date.getTime()) / 1000); + const units: [number, string][] = [ + [31536000, 'year'], + [2592000, 'month'], + [86400, 'day'], + [3600, 'hour'], + [60, 'minute'], + ]; + for (const [threshold, unit] of units) { + if (secondsAgo >= threshold) { + const count = Math.floor(secondsAgo / threshold); + return `${count} ${unit}${count !== 1 ? 's' : ''} ago`; } - - return `${minutes} minutes ago`; - } else if (secondsAgo < 86400) { - const hours = Math.floor(secondsAgo / 3600); - - if (hours === 1) { - return "1 hour ago"; - } - - return `${hours} hours ago`; - } else if (secondsAgo < 2592000) { - const days = Math.floor(secondsAgo / 86400); - - if (days === 1) { - return "1 day ago"; - } - - return `${days} days ago`; - } else if (secondsAgo < 31536000) { - const months = Math.floor(secondsAgo / 2592000); - - if (months === 1) { - return "1 month ago"; - } - - return `${months} months ago`; - } else { - const years = Math.floor(secondsAgo / 31536000); - - if (years === 1) { - return "1 year ago"; - } - - return `${years} years ago`; } + const s = Math.max(0, secondsAgo); + return `${s} second${s !== 1 ? 's' : ''} ago`; } -export { - formatTime, - stringToDate, - dateToTimeAgo -} \ No newline at end of file +export { formatTime, stringToDate, dateToTimeAgo };