saifisvibin's picture
Add password protection with APP_PASSWORD environment variable
87ae9fd
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;