diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index d648ade..3dd46ba 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -7,6 +7,7 @@ import Home from './pages/Home'; import {useEffect} from "react"; import MyClips from './pages/MyClips'; import VideoPlayer from "./pages/VideoPlayer.tsx"; +import EditClip from "./pages/EditClip.tsx"; import Profile from "./pages/Profile.tsx"; import { GoogleOAuthProvider } from '@react-oauth/google'; @@ -26,6 +27,7 @@ function App() { } /> } /> } /> + } /> } /> diff --git a/frontend/src/components/video/VideoCard.tsx b/frontend/src/components/video/VideoCard.tsx index a635ad4..c4131c5 100644 --- a/frontend/src/components/video/VideoCard.tsx +++ b/frontend/src/components/video/VideoCard.tsx @@ -3,13 +3,14 @@ import { formatTime, stringToDate, dateToTimeAgo } from "../../utils/utils.ts"; import { Link } from "react-router-dom"; import { useEffect, useRef, useState } from "react"; import { isThumbnailAvailable } from "../../utils/endpoints.ts"; -import { MoreVertical, Trash2 } from "lucide-react"; +import { MoreVertical, Pencil, Trash2 } from "lucide-react"; type VideoCardProps = { id: number, title: string, duration: number, createdAt: string, + onEdit?: () => void, onDelete?: () => void, className?: string, } @@ -17,7 +18,7 @@ type VideoCardProps = { const fallbackThumbnail = "../../../public/default_thumbnail.png"; const API_URL = import.meta.env.VITE_API_URL; -const VideoCard = ({ id, title, duration, createdAt, onDelete, className }: VideoCardProps) => { +const VideoCard = ({ id, title, duration, createdAt, onEdit, onDelete, className }: VideoCardProps) => { const [timeAgo, setTimeAgo] = useState(dateToTimeAgo(stringToDate(createdAt))); const [thumbnailAvailable, setThumbnailAvailable] = useState(true); const [menuOpen, setMenuOpen] = useState(false); @@ -66,7 +67,7 @@ const VideoCard = ({ id, title, duration, createdAt, onDelete, className }: Vide {formatTime(duration)} - {onDelete && ( + {(onEdit || onDelete) && (
- {!confirmDelete && ( - - )} + {!confirmDelete && (<> + {onEdit && ( + + )} + {onDelete && ( + + )} + )} {confirmDelete && (

Delete this clip?

diff --git a/frontend/src/pages/EditClip.tsx b/frontend/src/pages/EditClip.tsx new file mode 100644 index 0000000..58d86b1 --- /dev/null +++ b/frontend/src/pages/EditClip.tsx @@ -0,0 +1,108 @@ +import { useEffect, useState } from "react"; +import { useNavigate, useParams } from "react-router-dom"; +import { ArrowLeft, Check } from "lucide-react"; +import { getClipById, patchClip } from "../utils/endpoints.ts"; +import Box from "../components/Box.tsx"; +import BlueButton from "../components/buttons/BlueButton.tsx"; + +const inputClass = "border border-gray-300 bg-white rounded-md w-full p-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-colors"; + +const EditClip = () => { + const { id } = useParams(); + const navigate = useNavigate(); + + const [title, setTitle] = useState(""); + const [description, setDescription] = useState(""); + const [loading, setLoading] = useState(true); + const [saving, setSaving] = useState(false); + const [saved, setSaved] = useState(false); + const [error, setError] = useState(null); + + useEffect(() => { + if (!id) return; + getClipById(id) + .then((clip) => { + if (clip) { + setTitle(clip.title ?? ""); + setDescription(clip.description ?? ""); + } + }) + .catch(() => setError("Failed to load clip.")) + .finally(() => setLoading(false)); + }, [id]); + + const handleSave = async () => { + if (!id) return; + setSaving(true); + setError(null); + try { + await patchClip(Number(id), { title, description }); + setSaved(true); + setTimeout(() => setSaved(false), 2000); + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to save."); + } finally { + setSaving(false); + } + }; + + return ( +
+ + +

Edit Clip

+ + {loading ? ( +
Loading...
+ ) : ( + +
+ + setTitle(e.target.value)} + placeholder="Enter title" + className={inputClass} + /> +
+ +
+ +