113 lines
4.2 KiB
TypeScript
113 lines
4.2 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { createBrowserClient } from "@supabase/ssr";
|
|
import { toast } from "sonner"; // or use alert()
|
|
|
|
interface VideoUploadProps {
|
|
lessonId: string;
|
|
currentVideoUrl?: string | null;
|
|
onUploadComplete: (url: string) => void; // Callback to save to DB
|
|
}
|
|
|
|
export default function VideoUpload({ lessonId, currentVideoUrl, onUploadComplete }: VideoUploadProps) {
|
|
const [uploading, setUploading] = useState(false);
|
|
const [progress, setProgress] = useState(0);
|
|
|
|
const handleVideoUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const file = e.target.files?.[0];
|
|
if (!file) return;
|
|
|
|
setUploading(true);
|
|
setProgress(0);
|
|
|
|
const supabase = createBrowserClient(
|
|
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
|
|
);
|
|
|
|
// 1. Verify session
|
|
const { data: { user }, error: authError } = await supabase.auth.getUser();
|
|
if (authError || !user) {
|
|
toast.error("Authentication error: Please log in again.");
|
|
setUploading(false);
|
|
return;
|
|
}
|
|
|
|
console.log("Authenticated User ID:", user.id);
|
|
|
|
// 2. Create a unique file path: lesson_id/timestamp_filename
|
|
const filePath = `${lessonId}/${Date.now()}_${file.name}`;
|
|
|
|
// 3. Upload to Supabase Storage
|
|
const { error } = await supabase.storage
|
|
.from("courses")
|
|
.upload(filePath, file, {
|
|
upsert: true,
|
|
});
|
|
|
|
if (error) {
|
|
console.error("Storage upload error:", error);
|
|
// Hint for common RLS issue
|
|
if (error.message.includes("row-level security policy")) {
|
|
toast.error("Upload failed: RLS Policy error. Make sure 'courses' bucket allows uploads for authenticated teachers.");
|
|
} else {
|
|
toast.error("Upload failed: " + error.message);
|
|
}
|
|
setUploading(false);
|
|
return;
|
|
}
|
|
|
|
// 4. Get Public URL
|
|
const { data: { publicUrl } } = supabase.storage
|
|
.from("courses")
|
|
.getPublicUrl(filePath);
|
|
|
|
onUploadComplete(publicUrl);
|
|
setUploading(false);
|
|
toast.success("Video subido con éxito");
|
|
};
|
|
|
|
return (
|
|
<div className="border border-slate-200 rounded-lg p-6 bg-slate-50">
|
|
<h3 className="font-medium text-slate-900 mb-2">Video de la Lección</h3>
|
|
|
|
{currentVideoUrl ? (
|
|
<div className="mb-4 aspect-video bg-black rounded-md overflow-hidden relative group">
|
|
<video src={currentVideoUrl} controls className="w-full h-full" />
|
|
<div className="absolute inset-0 bg-black/50 hidden group-hover:flex items-center justify-center">
|
|
<p className="text-white text-sm">Subir otro para reemplazar</p>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div className="mb-4 aspect-video bg-slate-200 rounded-md flex items-center justify-center text-slate-400">
|
|
Sin video
|
|
</div>
|
|
)}
|
|
|
|
<div className="flex flex-col gap-2">
|
|
<label className="block w-full">
|
|
<span className="sr-only">Choose video</span>
|
|
<input
|
|
type="file"
|
|
accept="video/*"
|
|
onChange={handleVideoUpload}
|
|
disabled={uploading}
|
|
className="block w-full text-sm text-slate-500
|
|
file:mr-4 file:py-2 file:px-4
|
|
file:rounded-full file:border-0
|
|
file:text-sm file:font-semibold
|
|
file:bg-black file:text-white
|
|
hover:file:bg-slate-800
|
|
"
|
|
/>
|
|
</label>
|
|
{uploading && (
|
|
<div className="w-full bg-slate-200 rounded-full h-2 mt-2">
|
|
<div className="bg-black h-2 rounded-full transition-all" style={{ width: `${progress}%` }}></div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
} |