Spaces:
Sleeping
Sleeping
| import React, { useState, useEffect } from 'react'; | |
| import axios from 'axios'; | |
| import UploadStep from './components/UploadStep'; | |
| import DesignStep from './components/DesignStep'; | |
| import ReviewSendStep from './components/ReviewSendStep'; | |
| import LoginScreen from './components/LoginScreen'; | |
| import { motion, AnimatePresence } from 'framer-motion'; | |
| import { FaCheckCircle } from 'react-icons/fa'; | |
| function App() { | |
| const [isAuthenticated, setIsAuthenticated] = useState(false); | |
| const [authChecked, setAuthChecked] = useState(false); | |
| const [step, setStep] = useState(1); | |
| const [fileData, setFileData] = useState(null); | |
| const [designParams, setDesignParams] = useState({ | |
| name_color: "#000000", | |
| font_size: 60, | |
| x: null, | |
| y: null, | |
| fontname: "helv" | |
| }); | |
| const [mappings, setMappings] = useState({ | |
| name_column: "", | |
| email_column: "" | |
| }); | |
| // Check authentication status on mount | |
| useEffect(() => { | |
| const checkAuth = async () => { | |
| // First check sessionStorage for existing auth | |
| if (sessionStorage.getItem('auth_verified') === 'true') { | |
| setIsAuthenticated(true); | |
| setAuthChecked(true); | |
| return; | |
| } | |
| // Check if auth is required | |
| try { | |
| const response = await axios.get('/api/auth/status'); | |
| if (!response.data.auth_required) { | |
| // No password set, allow access | |
| setIsAuthenticated(true); | |
| } | |
| } catch (err) { | |
| console.error('Auth check failed:', err); | |
| } | |
| setAuthChecked(true); | |
| }; | |
| checkAuth(); | |
| }, []); | |
| const handleAuthenticated = () => { | |
| setIsAuthenticated(true); | |
| }; | |
| const nextStep = () => setStep(step + 1); | |
| const prevStep = () => setStep(step - 1); | |
| const steps = [ | |
| { id: 1, title: "Upload" }, | |
| { id: 2, title: "Design" }, | |
| { id: 3, title: "Send" } | |
| ]; | |
| // Show loading while checking auth | |
| if (!authChecked) { | |
| return ( | |
| <div className="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100 flex items-center justify-center"> | |
| <div className="text-center"> | |
| <div className="w-12 h-12 border-4 border-blue-600 border-t-transparent rounded-full animate-spin mx-auto mb-4"></div> | |
| <p className="text-gray-600">Loading...</p> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| // Show login screen if not authenticated | |
| if (!isAuthenticated) { | |
| return <LoginScreen onAuthenticated={handleAuthenticated} />; | |
| } | |
| return ( | |
| <div className="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100"> | |
| {/* Header */} | |
| <div className="bg-white border-b border-gray-200 shadow-sm"> | |
| <div className="max-w-6xl mx-auto px-6 py-6"> | |
| <div className="flex items-center justify-between"> | |
| <div> | |
| <h1 className="text-3xl font-bold text-gray-900">Certificate Manager</h1> | |
| <p className="text-gray-600 mt-1">Professional certificate generation and distribution</p> | |
| </div> | |
| <div className="text-right"> | |
| <div className="text-sm text-gray-500">Step {step} of 3</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div className="max-w-6xl mx-auto px-6 py-8"> | |
| {/* Progress Steps */} | |
| <div className="mb-10"> | |
| <div className="flex items-center justify-between"> | |
| {steps.map((s, index) => ( | |
| <React.Fragment key={s.id}> | |
| <div className="flex flex-col items-center flex-1"> | |
| <motion.div | |
| initial={false} | |
| animate={{ | |
| scale: step === s.id ? 1.1 : 1 | |
| }} | |
| className={`w-12 h-12 rounded-full flex items-center justify-center font-bold text-lg transition-all duration-300 ${step > s.id | |
| ? 'bg-green-500 text-white shadow-lg' | |
| : step === s.id | |
| ? 'bg-blue-600 text-white shadow-lg shadow-blue-200' | |
| : 'bg-gray-200 text-gray-500' | |
| }`} | |
| > | |
| {step > s.id ? <FaCheckCircle className="text-2xl" /> : s.id} | |
| </motion.div> | |
| <div className={`mt-2 text-sm font-medium ${step >= s.id ? 'text-gray-900' : 'text-gray-500' | |
| }`}> | |
| {s.title} | |
| </div> | |
| </div> | |
| {index < steps.length - 1 && ( | |
| <div className="flex-1 h-1 mx-4 mt-[-20px]"> | |
| <div className={`h-full rounded transition-all duration-500 ${step > s.id ? 'bg-green-500' : 'bg-gray-200' | |
| }`}></div> | |
| </div> | |
| )} | |
| </React.Fragment> | |
| ))} | |
| </div> | |
| </div> | |
| {/* Main Content */} | |
| <motion.div | |
| layout | |
| className="bg-white rounded-xl shadow-lg border border-gray-200 overflow-hidden" | |
| > | |
| <div className="p-10"> | |
| <AnimatePresence mode="wait"> | |
| <motion.div | |
| key={step} | |
| initial={{ opacity: 0, y: 10 }} | |
| animate={{ opacity: 1, y: 0 }} | |
| exit={{ opacity: 0, y: -10 }} | |
| transition={{ duration: 0.2 }} | |
| > | |
| {step === 1 && ( | |
| <UploadStep | |
| onUploadSuccess={(data) => { | |
| setFileData(data); | |
| nextStep(); | |
| }} | |
| /> | |
| )} | |
| {step === 2 && ( | |
| <DesignStep | |
| fileData={fileData} | |
| designParams={designParams} | |
| setDesignParams={setDesignParams} | |
| mappings={mappings} | |
| setMappings={setMappings} | |
| onNext={nextStep} | |
| onBack={prevStep} | |
| /> | |
| )} | |
| {step === 3 && ( | |
| <ReviewSendStep | |
| fileData={fileData} | |
| designParams={designParams} | |
| mappings={mappings} | |
| onBack={prevStep} | |
| /> | |
| )} | |
| </motion.div> | |
| </AnimatePresence> | |
| </div> | |
| </motion.div> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| export default App; | |