From 265d94999ad4498524f9881ca0feef775f1afb99 Mon Sep 17 00:00:00 2001 From: Yared Yemane Date: Wed, 8 Apr 2026 01:11:17 -0700 Subject: [PATCH] improve intro video upload and preview in practice creation Import intro video URLs through /files/upload, keep Vimeo-friendly URL handling, and render inline video preview for uploaded/imported links in Step 1 context. Made-with: Cursor --- .../content-management/AddNewPracticePage.tsx | 98 ++++++++++++++++++- 1 file changed, 96 insertions(+), 2 deletions(-) diff --git a/src/pages/content-management/AddNewPracticePage.tsx b/src/pages/content-management/AddNewPracticePage.tsx index 3d8f0ef..bdb8ce0 100644 --- a/src/pages/content-management/AddNewPracticePage.tsx +++ b/src/pages/content-management/AddNewPracticePage.tsx @@ -73,6 +73,29 @@ function introVideoUrlFromUploadResponse(data: { url?: string; embed_url?: strin return pageUrl || null } +function toVimeoEmbedUrl(rawUrl: string): string | null { + try { + const parsed = new URL(rawUrl.trim()) + const host = parsed.hostname.toLowerCase() + if (!host.includes("vimeo.com")) return null + if (host.includes("player.vimeo.com") && parsed.pathname.includes("/video/")) return parsed.toString() + const segments = parsed.pathname.split("/").filter(Boolean) + const videoId = segments.find((segment) => /^\d+$/.test(segment)) + if (!videoId) return null + const hash = parsed.searchParams.get("h") + return hash + ? `https://player.vimeo.com/video/${videoId}?h=${encodeURIComponent(hash)}` + : `https://player.vimeo.com/video/${videoId}` + } catch { + return null + } +} + +function isDirectVideoFile(url: string): boolean { + const clean = url.split("?")[0].toLowerCase() + return /\.(mp4|webm|ogg|mov|m4v)$/.test(clean) +} + function createEmptyQuestion(id: string): Question { return { id, @@ -120,6 +143,7 @@ export function AddNewPracticePage() { const [practiceDescription, setPracticeDescription] = useState("") const [introVideoUrl, setIntroVideoUrl] = useState("") const [uploadingIntroVideo, setUploadingIntroVideo] = useState(false) + const [importingIntroVideoUrl, setImportingIntroVideoUrl] = useState(false) const introVideoFileInputRef = useRef(null) const [shuffleQuestions, setShuffleQuestions] = useState(false) const [passingScore, setPassingScore] = useState(50) @@ -175,6 +199,37 @@ export function AddNewPracticePage() { } } + const handleImportIntroVideoFromUrl = async () => { + const source = introVideoUrl.trim() + if (!source || !/^https?:\/\//i.test(source)) return + + setImportingIntroVideoUrl(true) + try { + const uploadRes = await uploadVideoFile(source, { + title: practiceTitle.trim() || "Practice intro", + description: practiceDescription.trim() || undefined, + }) + const finalUrl = introVideoUrlFromUploadResponse(uploadRes.data?.data) + if (!finalUrl) throw new Error("Missing uploaded video url") + setIntroVideoUrl(finalUrl) + toast.success("Intro video URL imported", { description: "Processed via /files/upload." }) + } catch (error) { + console.error("Failed to import intro video URL:", error) + toast.error("Failed to import intro video URL") + } finally { + setImportingIntroVideoUrl(false) + } + } + + const introVideoPreview = useMemo(() => { + const raw = introVideoUrl.trim() + if (!raw) return null + const vimeoEmbedUrl = toVimeoEmbedUrl(raw) + if (vimeoEmbedUrl) return { kind: "vimeo" as const, url: vimeoEmbedUrl } + if (isDirectVideoFile(raw)) return { kind: "video" as const, url: raw } + return null + }, [introVideoUrl]) + const addQuestion = () => { setQuestions([...questions, createEmptyQuestion(String(Date.now()))]) } @@ -384,6 +439,7 @@ export function AddNewPracticePage() { setIntroVideoUrl(e.target.value)} + onBlur={() => void handleImportIntroVideoFromUrl()} placeholder="https://…" type="url" inputMode="url" @@ -396,14 +452,14 @@ export function AddNewPracticePage() { accept="video/*" className="hidden" onChange={handleIntroVideoFileChange} - disabled={uploadingIntroVideo} + disabled={uploadingIntroVideo || importingIntroVideoUrl} />
+ {introVideoUrl.trim() ? ( ) : null}
+ {introVideoPreview ? ( +
+

Preview

+ {introVideoPreview.kind === "vimeo" ? ( +
+