Spaces:
Sleeping
Sleeping
| import React, { useState } from 'react'; | |
| import axios from 'axios'; | |
| import { FaCloudUploadAlt, FaFileExcel, FaFilePdf, FaSpinner } from 'react-icons/fa'; | |
| import { motion } from 'framer-motion'; | |
| const UploadStep = ({ onUploadSuccess }) => { | |
| const [excelFile, setExcelFile] = useState(null); | |
| const [pdfFile, setPdfFile] = useState(null); | |
| const [loading, setLoading] = useState(false); | |
| const [error, setError] = useState(null); | |
| const handleUpload = async () => { | |
| if (!excelFile || !pdfFile) { | |
| setError("Please select both files."); | |
| return; | |
| } | |
| setLoading(true); | |
| setError(null); | |
| const formData = new FormData(); | |
| formData.append("excel_file", excelFile); | |
| formData.append("pdf_file", pdfFile); | |
| try { | |
| const response = await axios.post("/api/certificates/upload", formData, { | |
| headers: { "Content-Type": "multipart/form-data" } | |
| }); | |
| onUploadSuccess(response.data); | |
| } catch (err) { | |
| setError("Upload failed. Please try again."); | |
| console.error(err); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }; | |
| const FileInput = ({ accept, label, icon: Icon, file, setFile }) => ( | |
| <motion.div | |
| whileHover={{ y: -4 }} | |
| className={`relative border-2 border-dashed rounded-lg p-8 text-center transition-all cursor-pointer ${file ? 'border-blue-500 bg-blue-50' : 'border-gray-300 bg-white hover:border-blue-400 hover:bg-gray-50' | |
| }`} | |
| > | |
| <input | |
| type="file" | |
| accept={accept} | |
| onChange={(e) => setFile(e.target.files[0])} | |
| className="absolute inset-0 w-full h-full opacity-0 cursor-pointer z-10" | |
| /> | |
| <div className="flex flex-col items-center pointer-events-none"> | |
| <div className={`w-16 h-16 rounded-full flex items-center justify-center mb-4 ${file ? 'bg-blue-100' : 'bg-gray-100' | |
| }`}> | |
| <Icon className={`text-3xl ${file ? 'text-blue-600' : 'text-gray-400'}`} /> | |
| </div> | |
| <h3 className="text-base font-semibold text-gray-900 mb-1">{label}</h3> | |
| <p className="text-sm text-gray-500"> | |
| {file ? ( | |
| <span className="text-blue-600 font-medium">{file.name}</span> | |
| ) : ( | |
| "Click to browse or drag & drop" | |
| )} | |
| </p> | |
| </div> | |
| </motion.div> | |
| ); | |
| return ( | |
| <div className="max-w-3xl mx-auto"> | |
| <div className="text-center mb-8"> | |
| <h2 className="text-2xl font-bold text-gray-900 mb-2">Upload Files</h2> | |
| <p className="text-gray-600">Upload your attendee list and certificate template to begin.</p> | |
| </div> | |
| <div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8"> | |
| <FileInput | |
| accept=".xlsx" | |
| label="Attendee Data (.xlsx)" | |
| icon={FaFileExcel} | |
| file={excelFile} | |
| setFile={setExcelFile} | |
| /> | |
| <FileInput | |
| accept=".pdf" | |
| label="Certificate Template (.pdf)" | |
| icon={FaFilePdf} | |
| file={pdfFile} | |
| setFile={setPdfFile} | |
| /> | |
| </div> | |
| {error && ( | |
| <motion.div | |
| initial={{ opacity: 0, y: -10 }} | |
| animate={{ opacity: 1, y: 0 }} | |
| className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-lg mb-6 text-center text-sm" | |
| > | |
| {error} | |
| </motion.div> | |
| )} | |
| <div className="flex justify-center"> | |
| <motion.button | |
| whileHover={{ scale: 1.02 }} | |
| whileTap={{ scale: 0.98 }} | |
| onClick={handleUpload} | |
| disabled={loading} | |
| className={`flex items-center justify-center space-x-2 py-3 px-8 rounded-lg text-white font-semibold shadow-md transition-all ${loading ? 'bg-gray-400 cursor-not-allowed' : 'bg-blue-600 hover:bg-blue-700 hover:shadow-lg' | |
| }`} | |
| > | |
| {loading ? ( | |
| <> | |
| <FaSpinner className="animate-spin" /> | |
| <span>Processing...</span> | |
| </> | |
| ) : ( | |
| <> | |
| <FaCloudUploadAlt className="text-xl" /> | |
| <span>Upload & Continue</span> | |
| </> | |
| )} | |
| </motion.button> | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| export default UploadStep; | |