Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 38 additions & 51 deletions src/components/FileUpload.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { useRef, useState, useEffect, useCallback } from "react";
import { Film, FolderOpen } from "lucide-react";
import { useEffect, useRef, useState } from "react";
import { FolderOpen, Film } from "lucide-react";
import LottiePlayer from "./LottiePlayer";
import uploadAnim from "@/lib/lottie/upload.json";
import { cn, formatBytes, formatDuration } from "@/lib/utils";
Expand All @@ -21,21 +21,19 @@ export default function FileUpload({
duration,
}: Props) {
const inputRef = useRef<HTMLInputElement>(null);

const [dragging, setDragging] = useState(false);
const [pageDragging, setPageDragging] = useState(false);
const [error, setError] = useState("");
const [warning, setWarning] = useState("");
const dragCounterRef = useRef(0);

// ── Keyboard shortcut Ctrl+O ──────────────────────────
useEffect(() => {
const handler = (e: KeyboardEvent) => {
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "o") {
e.preventDefault();
inputRef.current?.click();
}
};

document.addEventListener("keydown", handler);
return () => document.removeEventListener("keydown", handler);
}, []);
Expand Down Expand Up @@ -117,23 +115,34 @@ export default function FileUpload({
onFileSelect(file);
}, [onFileSelect]);

// ── Drop zone (inner) handler ─────────────────────────
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];

if (file) handleFile(file);
e.target.value = "";
};

const handleDrop = (e: React.DragEvent) => {
e.preventDefault();
setDragging(false);
const file = e.dataTransfer.files?.[0];
if (file) handleFile(file);
};

// ── File info (shown after upload) ───────────────────
const openFilePicker = () => {
inputRef.current?.click();
};

const FileInfo = () => (
<div className="px-4 py-3 bg-film-50 border border-film-200 rounded-lg">
<div className="flex flex-col lg:flex-row lg:items-center gap-3">
<div className="flex items-start gap-3 flex-1 min-w-0">
<div className="hidden lg:flex items-center justify-center w-9 h-9 rounded-lg bg-[var(--surface)] border border-[var(--border)] shrink-0">
<Film size={16} className="text-film-600" />
</div>

<Film size={18} className="lg:hidden text-film-600 shrink-0 mt-0.5" />

<div className="flex-1 min-w-0">
<div className="flex flex-wrap items-center gap-2 mb-0.5">
<p className="text-sm font-semibold text-film-700 truncate max-w-[320px] xl:max-w-[420px]">
Expand All @@ -160,7 +169,7 @@ export default function FileUpload({

<button
type="button"
onClick={() => inputRef.current?.click()}
onClick={openFilePicker}
className="text-xs font-semibold text-film-600 hover:text-film-700 uppercase tracking-wide"
>
Change
Expand All @@ -175,21 +184,9 @@ export default function FileUpload({
{fileError && (
<p className="text-xs text-red-500 mt-2 font-medium">{fileError}</p>
)}

<input
ref={inputRef}
type="file"
accept="video/*"
className="hidden"
onChange={(e) => {
const f = e.target.files?.[0];
if (f) handleFile(f);
}}
/>
</div>
);

// ── Drop zone (inner) ─────────────────────────────────
const DropZone = () => (
<div
id="upload-zone"
Expand All @@ -202,10 +199,10 @@ export default function FileUpload({
}}
onDragLeave={() => setDragging(false)}
onDrop={handleDrop}
onClick={() => inputRef.current?.click()}
onClick={openFilePicker}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
inputRef.current?.click();
openFilePicker();
}
}}
className={cn(
Expand All @@ -226,9 +223,11 @@ export default function FileUpload({

<div className="text-center">
<p className="font-heading font-semibold text-[var(--text)] text-base">
{dragging ? "Release to upload" : "Drag & Drop your video here"}
{dragging ? "Release to upload" : "Drag & Drop your video in here"}
</p>

<p className="text-sm text-[var(--muted)] mt-1">or click to browse</p>

<p className="text-xs text-[var(--muted)] mt-2 font-heading">
Ctrl+O / Cmd+O
</p>
Expand All @@ -243,20 +242,13 @@ export default function FileUpload({
Supports: MP4, MOV, AVI, MKV, WebM, and most video formats up to 2GB
</p>

{fileError && (
<p className="text-sm text-red-500 text-center">{fileError}</p>
)}
{fileError && <p className="text-sm text-red-500 text-center">{fileError}</p>}

<input
ref={inputRef}
type="file"
accept="video/*"
className="hidden"
onChange={(e) => {
const f = e.target.files?.[0];
if (f) handleFile(f);
}}
/>
{currentFile && (
<p className="text-xs text-[var(--muted)] mt-2">
Selected: {formatBytes(currentFile.size)}
</p>
)}
</div>
);

Expand Down Expand Up @@ -293,20 +285,15 @@ export default function FileUpload({
</div>
)}

{/* ── Normal upload UI ── */}
<div className="space-y-2">
{error && (
<p role="alert" className="text-sm text-red-500">
{error}
</p>
)}
{warning && (
<p role="alert" className="text-sm text-yellow-500">
{warning}
</p>
)}
{currentFile ? <FileInfo /> : <DropZone />}
</div>
</>
{currentFile ? <FileInfo /> : <DropZone />}

<input
ref={inputRef}
type="file"
accept="video/*"
className="hidden"
onChange={handleInputChange}
/>
</div>
);
}