Files
Church-Music/new-site/frontend/src/components/home/QuickActions.jsx

256 lines
9.2 KiB
JavaScript

import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { motion, AnimatePresence } from "framer-motion";
import {
Upload,
Plus,
FileText,
Music,
X,
Check,
AlertCircle,
} from "lucide-react";
import toast from "react-hot-toast";
function QuickActions() {
const navigate = useNavigate();
const [showUploadModal, setShowUploadModal] = useState(false);
const [dragActive, setDragActive] = useState(false);
const [uploading, setUploading] = useState(false);
const [uploadedFile, setUploadedFile] = useState(null);
const handleDrag = (e) => {
e.preventDefault();
e.stopPropagation();
if (e.type === "dragenter" || e.type === "dragover") {
setDragActive(true);
} else if (e.type === "dragleave") {
setDragActive(false);
}
};
const handleDrop = (e) => {
e.preventDefault();
e.stopPropagation();
setDragActive(false);
if (e.dataTransfer.files && e.dataTransfer.files[0]) {
handleFile(e.dataTransfer.files[0]);
}
};
const handleFileInput = (e) => {
if (e.target.files && e.target.files[0]) {
handleFile(e.target.files[0]);
}
};
const handleFile = async (file) => {
// Check file type
const validTypes = ["text/plain", "application/pdf", ".docx", ".doc"];
const isValid = validTypes.some(
(type) => file.type.includes(type) || file.name.endsWith(type),
);
if (
!isValid &&
!file.name.endsWith(".txt") &&
!file.name.endsWith(".pdf")
) {
toast.error("Please upload a .txt, .pdf, or .docx file");
return;
}
setUploadedFile(file);
setUploading(true);
// Simulate upload
await new Promise((r) => setTimeout(r, 1500));
setUploading(false);
toast.success("File uploaded successfully!");
};
const handleProcessFile = () => {
// Navigate to song editor with parsed content
setShowUploadModal(false);
setUploadedFile(null);
navigate("/song/new");
toast.success("Creating new song from lyrics...");
};
return (
<>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{/* Upload Lyrics Tile */}
<motion.div
whileHover={{ scale: 1.02, y: -4 }}
whileTap={{ scale: 0.98 }}
onClick={() => setShowUploadModal(true)}
className="glass-card p-6 cursor-pointer group overflow-hidden relative"
>
<div className="absolute inset-0 bg-gradient-to-br from-purple-500/10 to-pink-500/10 opacity-0 group-hover:opacity-100 transition-opacity" />
<div className="relative">
<div className="w-14 h-14 rounded-2xl bg-gradient-to-br from-purple-400 to-pink-500 flex items-center justify-center mb-4 shadow-soft group-hover:shadow-lg transition-shadow">
<Upload className="w-7 h-7 text-white" />
</div>
<h3 className="text-lg font-semibold text-gray-800 mb-1">
Upload Lyrics
</h3>
<p className="text-gray-500 text-sm">
Import lyrics from .txt, .pdf, or .docx files
</p>
</div>
</motion.div>
{/* Create New Song Tile */}
<motion.div
whileHover={{ scale: 1.02, y: -4 }}
whileTap={{ scale: 0.98 }}
onClick={() => navigate("/song/new")}
className="glass-card p-6 cursor-pointer group overflow-hidden relative"
>
<div className="absolute inset-0 bg-gradient-to-br from-blue-500/10 to-cyan-500/10 opacity-0 group-hover:opacity-100 transition-opacity" />
<div className="relative">
<div className="w-14 h-14 rounded-2xl bg-gradient-to-br from-blue-400 to-cyan-500 flex items-center justify-center mb-4 shadow-soft group-hover:shadow-lg transition-shadow">
<Plus className="w-7 h-7 text-white" />
</div>
<h3 className="text-lg font-semibold text-gray-800 mb-1">
Create New Song
</h3>
<p className="text-gray-500 text-sm">
Start from scratch with our chord editor
</p>
</div>
</motion.div>
</div>
{/* Upload Modal */}
<AnimatePresence>
{showUploadModal && (
<>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black/40 backdrop-blur-sm z-50"
onClick={() => {
setShowUploadModal(false);
setUploadedFile(null);
}}
/>
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-full max-w-lg glass-card p-6 z-50"
>
<div className="flex items-center justify-between mb-4">
<h2 className="text-xl font-bold text-gray-800">
Upload Lyrics
</h2>
<button
onClick={() => {
setShowUploadModal(false);
setUploadedFile(null);
}}
className="p-2 hover:bg-gray-100 rounded-xl transition-colors"
>
<X className="w-5 h-5 text-gray-500" />
</button>
</div>
{!uploadedFile ? (
<div
onDragEnter={handleDrag}
onDragLeave={handleDrag}
onDragOver={handleDrag}
onDrop={handleDrop}
className={`border-2 border-dashed rounded-2xl p-8 text-center transition-colors ${
dragActive
? "border-primary-500 bg-primary-50"
: "border-gray-300 hover:border-gray-400"
}`}
>
<div className="w-16 h-16 mx-auto mb-4 rounded-2xl bg-gray-100 flex items-center justify-center">
<FileText className="w-8 h-8 text-gray-400" />
</div>
<p className="text-gray-600 mb-2">
Drag and drop your file here, or{" "}
<label className="text-primary-600 font-medium cursor-pointer hover:underline">
browse
<input
type="file"
accept=".txt,.pdf,.doc,.docx"
onChange={handleFileInput}
className="hidden"
/>
</label>
</p>
<p className="text-sm text-gray-400">
Supports .txt, .pdf, .docx files
</p>
</div>
) : (
<div className="space-y-4">
<div className="flex items-center gap-4 p-4 bg-gray-50 rounded-xl">
<div className="w-12 h-12 rounded-xl bg-green-100 flex items-center justify-center">
{uploading ? (
<div className="w-5 h-5 border-2 border-green-600 border-t-transparent rounded-full animate-spin" />
) : (
<Check className="w-6 h-6 text-green-600" />
)}
</div>
<div className="flex-1 min-w-0">
<p className="font-medium text-gray-800 truncate">
{uploadedFile.name}
</p>
<p className="text-sm text-gray-500">
{uploading ? "Uploading..." : "Ready to process"}
</p>
</div>
{!uploading && (
<button
onClick={() => setUploadedFile(null)}
className="p-2 hover:bg-gray-200 rounded-lg transition-colors"
>
<X className="w-4 h-4 text-gray-500" />
</button>
)}
</div>
<div className="flex items-start gap-3 p-3 bg-blue-50 rounded-xl">
<AlertCircle className="w-5 h-5 text-blue-600 flex-shrink-0 mt-0.5" />
<p className="text-sm text-blue-700">
We'll try to detect sections (Verse, Chorus, etc.) and
existing chord notations automatically.
</p>
</div>
<div className="flex gap-3">
<button
onClick={() => setUploadedFile(null)}
className="btn-ghost flex-1"
>
Upload Different File
</button>
<button
onClick={handleProcessFile}
disabled={uploading}
className="btn-primary flex-1"
>
<Music className="w-4 h-4 mr-2" />
Create Song
</button>
</div>
</div>
)}
</motion.div>
</>
)}
</AnimatePresence>
</>
);
}
export default QuickActions;