import React, { useState, useEffect } from 'react'; import { initializeApp } from 'firebase/app'; import { getAuth, signInWithCustomToken, signInAnonymously, onAuthStateChanged } from 'firebase/auth'; import { getFirestore, collection, addDoc, onSnapshot, serverTimestamp, query, doc, getDoc, setDoc } from 'firebase/firestore'; // Main App component const App = () => { // --- Global Configuration & State --- const ADMIN_PASSWORD = 'password123'; // State for Firebase and authentication const [db, setDb] = useState(null); const [auth, setAuth] = useState(null); const [userId, setUserId] = useState(null); const [isAuthReady, setIsAuthReady] = useState(false); const [isAdmin, setIsAdmin] = useState(false); const [loading, setLoading] = useState(true); // State for UI navigation and content const [currentView, setCurrentView] = useState('client-login'); const [clientProposalData, setClientProposalData] = useState(null); const [currentProposalId, setCurrentProposalId] = useState(''); const [linkGenerationStatus, setLinkGenerationStatus] = useState(''); const [shortCode, setShortCode] = useState(''); const [isLoading, setIsLoading] = useState(false); // State for the Admin Dashboard const [responses, setResponses] = useState([]); const [sortKey, setSortKey] = useState('timestamp'); const [filterAction, setFilterAction] = useState('all'); // State for the Proposal Generator Form const [form, setForm] = useState({ companyName: '', cultivatorLicense: '', cultivatorAddress: '', cultivatorContactName: '', cultivatorContactEmail: '', cultivatorContactPhone: '', totalBatches: 1, totalUnits: 1, engagement: '30', engagementStartDate: '', overridePrice: '', cleanSlateContactName: 'JANE DOE', cleanSlateContactEmail: 'JANE.DOE@CLEANSLATE.COM', cleanSlateContactPhone: '(555) 555-5555', proposalRecipientEmail: 'PROPOSALS@CLEANSLATE.COM', selectedCoreServices: [ "SEED-TO-SALE SUPPORT", "PR SUPPORT", "FORENSIC IMPACT REPORT", "INCIDENT REPORT & CAPA", "RECALL EMPLOYEE CERTIFICATIONS" ], selectedAdditionalServices: [], }); // State for the Modal const [isModalOpen, setIsModalOpen] = useState(false); const [modalAction, setModalAction] = useState(''); const [modalDetails, setModalDetails] = useState({ title: '', message: '', name: '', email: '', comments: '', submissionStatus: '', }); // --- Firebase Initialization and Authentication Listener --- useEffect(() => { // Use direct access to global variables const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id'; const firebaseConfig = typeof __firebase_config !== 'undefined' ? JSON.parse(__firebase_config) : {}; const initialAuthToken = typeof __initial_auth_token !== 'undefined' ? __initial_auth_token : null; if (Object.keys(firebaseConfig).length === 0) { console.error("Firebase config is missing."); setLoading(false); setIsAuthReady(true); return; } try { const firebaseApp = initializeApp(firebaseConfig); const firestoreDb = getFirestore(firebaseApp); const firebaseAuth = getAuth(firebaseApp); setDb(firestoreDb); setAuth(firebaseAuth); const signIn = async () => { try { if (initialAuthToken) { await signInWithCustomToken(firebaseAuth, initialAuthToken); } else { await signInAnonymously(firebaseAuth); } } catch (error) { console.error("Authentication failed:", error); await signInAnonymously(firebaseAuth); } }; signIn(); const unsubscribeAuth = onAuthStateChanged(firebaseAuth, (user) => { if (user) { setUserId(user.uid); } else { setUserId(crypto.randomUUID()); // Anonymous user ID } setIsAuthReady(true); setLoading(false); }); return () => unsubscribeAuth(); } catch (error) { console.error("Firebase initialization failed:", error); setIsAuthReady(true); setLoading(false); } }, []); // --- Dashboard Data Listener --- useEffect(() => { // Ensure authentication is ready and we have a user and db instance if (!isAuthReady || !db || !auth || !auth.currentUser) return; // Listen for proposal responses for the dashboard const appIdForDashboard = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id'; const collectionPathForDashboard = `/artifacts/${appIdForDashboard}/public/data/proposalresponses`; const q = query(collection(db, collectionPathForDashboard)); const unsubscribe = onSnapshot(q, (querySnapshot) => { const fetchedResponses = []; querySnapshot.forEach((doc) => { fetchedResponses.push({ id: doc.id, ...doc.data() }); }); setResponses(fetchedResponses); }, (error) => { console.error("Error fetching responses: ", error); }); return () => unsubscribe(); }, [isAuthReady, db, auth]); // --- UI Logic and Handlers --- const handleFormChange = (e) => { const { id, value, type, checked } = e.target; if (type === 'checkbox') { setForm(prevForm => { const updatedServices = prevForm[id].includes(value) ? prevForm[id].filter(s => s !== value) : [...prevForm[id], value]; return { ...prevForm, [id]: updatedServices }; }); } else { setForm(prevForm => ({ ...prevForm, [id]: type === 'number' ? parseFloat(value) : value, })); } }; const handleAdminLogin = (e) => { e.preventDefault(); const password = e.target.password.value; if (password === ADMIN_PASSWORD) { setIsAdmin(true); setCurrentView('generator'); } else { document.getElementById('admin-login-error').textContent = 'Incorrect password. Please try again.'; } }; const handleAdminLogout = () => { setIsAdmin(false); setCurrentView('client-login'); }; const handleGenerateShortCode = async () => { if (!isAuthReady || !db || !auth || !auth.currentUser || !userId) { setLinkGenerationStatus("ERROR: APPLICATION IS STILL LOADING. PLEASE WAIT A MOMENT AND TRY AGAIN."); return; } const proposalData = { ...form, createdAt: serverTimestamp(), createdBy: userId, }; if (!proposalData.companyName || !proposalData.cultivatorLicense) { setLinkGenerationStatus("ERROR: PLEASE FILL OUT COMPANY NAME AND LICENSE NUMBER."); return; } setIsLoading(true); setLinkGenerationStatus("GENERATING CODE..."); const newShortCode = generateShortCode(); setShortCode(newShortCode); try { const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id'; const collectionPath = `/artifacts/${appId}/public/data/proposals`; // Log the path segments for debugging console.log(`Path for setDoc: collectionPath='${collectionPath}', docId='${newShortCode}'`); const docRef = doc(db, collectionPath, newShortCode); await setDoc(docRef, proposalData); setLinkGenerationStatus("Short code generated!"); } catch (error) { console.error("Error generating short code: ", error); setLinkGenerationStatus("Failed to generate code."); } finally { setIsLoading(false); } }; const handleViewByShortCode = (e) => { e.preventDefault(); const code = e.target.shortCode.value; if (!code) { document.getElementById('short-code-error').textContent = 'Please enter a code.'; return; } document.getElementById('short-code-error').textContent = ''; setCurrentProposalId(code.toUpperCase()); setCurrentView('client-proposal-view'); }; const fetchClientProposal = async (proposalId) => { if (!isAuthReady || !db || !auth || !proposalId) { console.error("Firebase not ready or proposalId missing. Cannot fetch proposal."); return; } setIsLoading(true); const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id'; const docPath = `/artifacts/${appId}/public/data/proposals/${proposalId}`; // Log the full path for debugging console.log(`Path for getDoc: docPath='${docPath}'`); const proposalRef = doc(db, docPath); try { const proposalSnap = await getDoc(proposalRef); if (proposalSnap.exists()) { setClientProposalData(proposalSnap.data()); } else { setClientProposalData(null); } } catch (error) { console.error("Error fetching proposal: ", error); setClientProposalData(null); } finally { setIsLoading(false); } }; // Fetch proposal data when the view changes to client proposal view useEffect(() => { if (currentView === 'client-proposal-view' && currentProposalId) { fetchClientProposal(currentProposalId); } }, [currentView, currentProposalId, isAuthReady, db, auth]); const openModal = (action) => { setModalAction(action); const newModalDetails = { title: action === 'ACCEPT' ? 'ACCEPT PROPOSAL' : 'REVISE PROPOSAL', message: action === 'ACCEPT' ? 'PLEASE CONFIRM YOUR DETAILS TO ACCEPT THIS PROPOSAL. WE WILL BE IN TOUCH SHORTLY.' : 'PLEASE PROVIDE DETAILS ON THE REVISIONS YOU WOULD LIKE TO DISCUSS.', name: clientProposalData?.cultivatorContactName || '', email: clientProposalData?.cultivatorContactEmail || '', comments: '', submissionStatus: '', }; setModalDetails(newModalDetails); setIsModalOpen(true); }; const handleFormSubmit = async (e) => { e.preventDefault(); if (!isAuthReady || !db || !auth || !auth.currentUser || !userId) { setModalDetails(prev => ({ ...prev, submissionStatus: "ERROR: DATABASE NOT READY. PLEASE TRY AGAIN." })); return; } setModalDetails(prev => ({ ...prev, submissionStatus: "SUBMITTING..." })); const responseData = { userId: userId, action: modalAction, name: modalDetails.name, email: modalDetails.email, comments: modalAction === 'REVISE' ? modalDetails.comments : null, proposalId: currentProposalId, proposalDetails: clientProposalData, timestamp: serverTimestamp(), }; try { const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id'; const collectionPath = `/artifacts/${appId}/public/data/proposalresponses`; await addDoc(collection(db, collectionPath), responseData); setModalDetails(prev => ({ ...prev, submissionStatus: "SUCCESS! YOUR RESPONSE HAS BEEN RECORDED." })); setTimeout(() => { setIsModalOpen(false); }, 2000); } catch (error) { console.error("Error adding document: ", error); setModalDetails(prev => ({ ...prev, submissionStatus: "ERROR SUBMITTING RESPONSE." })); } }; // --- Price and Utility Functions --- const BASE_PRICES = { '30': 40000, '60': 75000, '90': 100000 }; const PRICE_PER_UNIT = 1; const PRICE_PER_BATCH = 10; const PRICE_PER_ADDITIONAL_SERVICE = 8000; const TOTAL_CORE_SERVICES_COUNT = 5; const calculatePrice = (proposal) => { const p = proposal || form; const selectedCoreServicesCount = (p.selectedCoreServices?.length) || 0; const selectedPaidAdditionalServicesCount = (p.selectedAdditionalServices?.filter(s => s !== 'INSURANCE ASSESSMENT').length) || 0; const priceMultiplier = selectedCoreServicesCount > 0 ? selectedCoreServicesCount / TOTAL_CORE_SERVICES_COUNT : 0; const calculatedPrice = (BASE_PRICES[p.engagement] * priceMultiplier) + ((p.totalUnits || 0) * PRICE_PER_UNIT) + ((p.totalBatches || 0) * PRICE_PER_BATCH) + (selectedPaidAdditionalServicesCount * PRICE_PER_ADDITIONAL_SERVICE); return p.overridePrice ? parseFloat(p.overridePrice) : calculatedPrice; }; const formatPrice = (price) => { if (typeof price === 'number' && !isNaN(price)) { return `$${price.toLocaleString('en-US', { minimumFractionDigits: 0, maximumFractionDigits: 0 })}`; } return 'N/A'; }; const generateShortCode = () => { const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; let result = ''; const charactersLength = characters.length; for (let i = 0; i < 6; i++) { result += characters.charAt(Math.floor(Math.random() * charactersLength)); } return result; }; // --- View Components --- const ClientLoginView = () => ( <div className="flex items-center justify-center min-h-screen p-4"> <div className="bg-white p-8 rounded-2xl shadow-xl border border-gray-200 w-full max-w-lg text-center"> <h1 className="text-3xl font-bold text-gray-800 mb-4">VIEW YOUR PROPOSAL</h1> <p className="text-gray-500 mb-6">Enter the 6-character code you received to view your personalized proposal.</p> <form onSubmit={handleViewByShortCode} className="flex flex-col sm:flex-row gap-2 max-w-sm mx-auto"> <input id="shortCode" type="text" placeholder="ENTER 6-DIGIT CODE" className="flex-1 p-3 rounded-md border-gray-300 shadow-sm border focus:ring-emerald-500 focus:border-emerald-500 text-center text-xl font-mono" /> <button type="submit" disabled={!isAuthReady} className="px-4 py-3 bg-purple-600 text-white font-bold rounded-full shadow-lg hover:bg-purple-700 transition disabled:bg-gray-400"> VIEW PROPOSAL </button> </form> <p id="short-code-error" className="text-red-500 text-sm mt-4"></p> <div className="mt-8 pt-6 border-t border-gray-200 flex justify-center"> <button onClick={() => setCurrentView('admin-login')} className="text-emerald-600 hover:underline text-sm font-medium"> ADMIN LOGIN </button> </div> </div> </div> ); const AdminLoginView = () => ( <div className="flex items-center justify-center min-h-screen"> <div className="bg-white p-8 rounded-2xl shadow-xl border border-gray-200 w-full max-w-sm text-center"> <h1 className="text-3xl font-bold text-gray-800 mb-4">ADMIN LOGIN</h1> <p className="text-gray-500 mb-6">Enter the password to access the admin portal.</p> <form id="admin-login-form" onSubmit={handleAdminLogin} className="space-y-4"> <input name="password" type="password" placeholder="ENTER PASSWORD" className="w-full p-3 rounded-md border-gray-300 shadow-sm p-2 border focus:ring-emerald-500 focus:border-emerald-500" required /> <p id="admin-login-error" className="text-red-500 text-sm"></p> <button type="submit" disabled={!isAuthReady} className="w-full px-4 py-3 bg-emerald-600 text-white font-bold rounded-full shadow-lg hover:bg-emerald-700 transition disabled:bg-gray-400"> LOGIN </button> </form> <button onClick={() => setCurrentView('client-login')} className="mt-4 text-sm text-gray-500 hover:underline"> ← BACK TO CLIENT VIEW </button> </div> </div> ); const AdminGeneratorView = () => ( <div className="bg-slate-100 min-h-screen"> <header className="bg-white shadow-sm p-4 flex justify-between items-center sticky top-0 z-10"> <h1 className="text-xl font-bold text-gray-800">CLEAN SLATE ADMIN</h1> <div className="flex items-center gap-4"> <span className="text-sm text-gray-600">Hello, Admin!</span> <button onClick={handleAdminLogout} className="px-4 py-2 bg-gray-200 text-gray-800 font-bold rounded-full shadow-lg hover:bg-gray-300 transition"> LOGOUT </button> </div> </header> <div className="p-8 flex items-center justify-center"> <div className="bg-white p-8 rounded-2xl shadow-xl border border-gray-200 flex flex-col lg:flex-row gap-8 w-full max-w-7xl"> <div className="flex-1 space-y-8"> <div className="text-center"> <h1 className="text-3xl font-bold text-gray-800 mb-2">CLEAN SLATE PROPOSAL GENERATOR</h1> <p className="text-gray-500">Create a customized recall management proposal in minutes.</p> <p className="text-gray-500 mt-2 text-xs">Your User ID: <span className="font-mono bg-gray-200 px-1 py-0.5 rounded text-gray-800">{userId}</span></p> <div className="flex flex-col sm:flex-row gap-2 justify-center mt-4"> <button onClick={() => setCurrentView('dashboard')} className="px-4 py-2 bg-emerald-600 text-white font-bold rounded-full shadow-lg hover:bg-emerald-700 transition"> VIEW DASHBOARD </button> <button onClick={handleGenerateShortCode} className="px-4 py-2 bg-blue-600 text-white font-bold rounded-full shadow-lg hover:bg-blue-700 disabled:bg-gray-400 transition" disabled={isLoading || !isAuthReady}> {isLoading ? 'SAVING...' : 'GENERATE SHORT CODE'} </button> </div> <div id="link-generation-status" className="mt-4 flex flex-col items-center"> {linkGenerationStatus && <p className={`text-sm ${linkGenerationStatus.includes('ERROR') || linkGenerationStatus.includes('FAILED') ? 'text-red-500' : 'text-green-600'} font-semibold`}>{linkGenerationStatus}</p>} {shortCode && <div className="flex flex-col w-full max-w-xl mt-2"> <p className="text-sm text-gray-500 mb-1 self-start">Share this code with your client:</p> <div className="flex w-full gap-2 items-center"> <div id="short-code-display" className="flex-1 p-2 bg-gray-100 rounded-md border border-gray-300 shadow-sm text-xl font-mono text-center tracking-widest">{shortCode}</div> <button onClick={() => { document.execCommand('copy'); setLinkGenerationStatus('COPIED TO CLIPBOARD!'); }} className="w-1/2 sm:w-auto bg-gray-200 text-gray-800 font-bold px-4 py-2 rounded-md hover:bg-gray-300">COPY</button> </div> </div> } </div> </div> <div className="grid md:grid-cols-2 gap-8"> <div className="bg-gray-50 p-6 rounded-xl shadow-inner col-span-2"> <h2 className="text-xl font-semibold text-gray-700 mb-4">CULTIVATOR DETAILS</h2> <div className="grid md:grid-cols-2 gap-4"> <div className="col-span-2 md:col-span-1"><label className="block text-sm font-medium text-gray-700">COMPANY NAME</label><input type="text" id="companyName" value={form.companyName} onChange={handleFormChange} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm p-2 border focus:ring-emerald-500 focus:border-emerald-500" placeholder="E.g., Alpine Strawberry" /></div> <div className="col-span-2 md:col-span-1"><label className="block text-sm font-medium text-gray-700">LICENSE NUMBER</label><input type="text" id="cultivatorLicense" value={form.cultivatorLicense} onChange={handleFormChange} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm p-2 border focus:ring-emerald-500 focus:border-emerald-500" placeholder="E.g., LCT-1234567" /></div> <div className="col-span-2"><label className="block text-sm font-medium text-gray-700">ADDRESS</label><input type="text" id="cultivatorAddress" value={form.cultivatorAddress} onChange={handleFormChange} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm p-2 border focus:ring-emerald-500 focus:border-emerald-500" placeholder="E.g., 123 Main St, Anytown, MA 12345" /></div> <div className="col-span-2 md:col-span-1"><label className="block text-sm font-medium text-gray-700">CONTACT NAME</label><input type="text" id="cultivatorContactName" value={form.cultivatorContactName} onChange={handleFormChange} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm p-2 border focus:ring-emerald-500 focus:border-emerald-500" placeholder="E.g., John Smith" /></div> <div className="col-span-2 md:col-span-1"><label className="block text-sm font-medium text-gray-700">CONTACT EMAIL</label><input type="email" id="cultivatorContactEmail" value={form.cultivatorContactEmail} onChange={handleFormChange} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm p-2 border focus:ring-emerald-500 focus:border-emerald-500" placeholder="E.g., john.smith@company.com" /></div> <div className="col-span-2 md:col-span-1"><label className="block text-sm font-medium text-gray-700">CONTACT PHONE</label><input type="tel" id="cultivatorContactPhone" value={form.cultivatorContactPhone} onChange={handleFormChange} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm p-2 border focus:ring-emerald-500 focus:border-emerald-500" placeholder="E.g., (555) 555-5555" /></div> <div className="col-span-2 md:col-span-1"><label className="block text-sm font-medium text-gray-700">NUMBER OF BATCHES</label><input type="number" id="totalBatches" min="1" value={form.totalBatches} onChange={handleFormChange} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm p-2 border focus:ring-emerald-500 focus:border-emerald-500" /></div> <div className="col-span-2 md:col-span-1"><label className="block text-sm font-medium text-gray-700">NUMBER OF UNITS</label><input type="number" id="totalUnits" min="1" value={form.totalUnits} onChange={handleFormChange} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm p-2 border focus:ring-emerald-500 focus:border-emerald-500" /></div> </div> </div> <div className="bg-gray-50 p-6 rounded-xl shadow-inner col-span-2"> <h2 className="text-xl font-semibold text-gray-700 mb-4">SELECT SERVICES</h2> <h3 className="text-lg font-medium text-gray-600 mb-2">CORE SERVICES</h3> <div className="space-y-3 mb-6"> {["SEED-TO-SALE SUPPORT", "PR SUPPORT", "FORENSIC IMPACT REPORT", "INCIDENT REPORT & CAPA", "RECALL EMPLOYEE CERTIFICATIONS"].map(service => ( <div className="flex items-center" key={service}> <input type="checkbox" id="selectedCoreServices" value={service} checked={form.selectedCoreServices.includes(service)} onChange={handleFormChange} className="h-4 w-4 text-emerald-600 border-gray-300 rounded focus:ring-emerald-500" /> <label htmlFor={`selectedCoreServices-${service}`} className="ml-3 text-sm text-gray-700 font-medium">{service}</label> </div> ))} </div> <h3 className="text-lg font-medium text-gray-600 mb-2">ADDITIONAL SERVICES</h3> <div className="space-y-3"> {["MARKETING SUPPORT", "LEGAL CONSULTATION", "ACCOUNTING SERVICES", "INSURANCE ASSESSMENT"].map(service => ( <div className="flex items-center" key={service}> <input type="checkbox" id="selectedAdditionalServices" value={service} checked={form.selectedAdditionalServices.includes(service)} onChange={handleFormChange} className="h-4 w-4 text-emerald-600 border-gray-300 rounded focus:ring-emerald-500" /> <label htmlFor={`selectedAdditionalServices-${service}`} className="ml-3 text-sm text-gray-700 font-medium">{service} {service === 'INSURANCE ASSESSMENT' ? '(FREE)' : ''}</label> </div> ))} </div> </div> <div className="bg-gray-50 p-6 rounded-xl shadow-inner col-span-2"> <h2 className="text-xl font-semibold text-gray-700 mb-4">ENGAGEMENT DETAILS</h2> <div className="grid md:grid-cols-2 gap-4"> <div><label className="block text-sm font-medium text-gray-700">ENGAGEMENT START DATE</label><input type="date" id="engagementStartDate" value={form.engagementStartDate} onChange={handleFormChange} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm p-2 border focus:ring-emerald-500 focus:border-emerald-500" /></div> <div> <label className="block text-sm font-medium text-gray-700">ENGAGEMENT LENGTH</label> <select id="engagement" value={form.engagement} onChange={handleFormChange} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm p-2 border focus:ring-emerald-500 focus:border-emerald-500"> <option value="30">30-Day Retainer</option> <option value="60">60-Day Retainer</option> <option value="90">90-Day Retainer</option> </select> </div> <div className="col-span-2"><label className="block text-sm font-medium text-gray-700">OVERRIDE PRICE (OPTIONAL)</label><input type="number" id="overridePrice" min="0" value={form.overridePrice} onChange={handleFormChange} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm p-2 border focus:ring-emerald-500 focus:border-emerald-500" placeholder="E.g., 50000" /></div> </div> </div> <div className="bg-gray-50 p-6 rounded-xl shadow-inner col-span-2"> <h2 className="text-xl font-semibold text-gray-700 mb-4">CLEAN SLATE CONTACT INFO</h2> <div className="grid md:grid-cols-2 gap-4"> <div className="col-span-2 md:col-span-1"><label className="block text-sm font-medium text-gray-700">CONTACT NAME</label><input type="text" id="cleanSlateContactName" value={form.cleanSlateContactName} onChange={handleFormChange} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm p-2 border focus:ring-emerald-500 focus:border-emerald-500" placeholder="E.g., Jane Doe" /></div> <div className="col-span-2 md:col-span-1"><label className="block text-sm font-medium text-gray-700">EMAIL</label><input type="email" id="cleanSlateContactEmail" value={form.cleanSlateContactEmail} onChange={handleFormChange} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm p-2 border focus:ring-emerald-500 focus:border-emerald-500" placeholder="E.g., jane.doe@cleanslate.com" /></div> <div className="col-span-2 md:col-span-1"><label className="block text-sm font-medium text-gray-700">PHONE</label><input type="tel" id="cleanSlateContactPhone" value={form.cleanSlateContactPhone} onChange={handleFormChange} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm p-2 border focus:ring-emerald-500 focus:border-emerald-500" placeholder="E.g., (555) 555-5555" /></div> <div className="col-span-2 md:col-span-1"><label className="block text-sm font-medium text-gray-700">PROPOSAL RECIPIENT EMAIL</label><input type="email" id="proposalRecipientEmail" value={form.proposalRecipientEmail} onChange={handleFormChange} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm p-2 border focus:ring-emerald-500 focus:border-emerald-500" placeholder="E.g., proposals@cleanslate.com" /></div> </div> </div> </div> </div> <ProposalPreview proposal={form} isClient={false} /> </div> </div> </div> ); const AdminDashboardView = () => { const sortedResponses = [...responses].sort((a, b) => { if (sortKey === 'timestamp') { const dateA = a.timestamp?.toDate() || 0; const dateB = b.timestamp?.toDate() || 0; return dateB - dateA; } else if (sortKey === 'companyName') { return (a.proposalDetails?.companyName || '').localeCompare(b.proposalDetails?.companyName || ''); } return 0; }); const filteredResponses = sortedResponses.filter(response => { if (filterAction === 'all') return true; return response.action === filterAction; }); return ( <div className="bg-slate-100 min-h-screen"> <header className="bg-white shadow-sm p-4 flex justify-between items-center sticky top-0 z-10"> <h1 className="text-xl font-bold text-gray-800">CLIENT RESPONSES DASHBOARD</h1> <div className="flex items-center gap-4"> <button onClick={() => setCurrentView('generator')} className="px-4 py-2 bg-gray-200 text-gray-800 font-bold rounded-full shadow-lg hover:bg-gray-300 transition"> ← BACK </button> <button onClick={handleAdminLogout} className="px-4 py-2 bg-gray-200 text-gray-800 font-bold rounded-full shadow-lg hover:bg-gray-300 transition"> LOGOUT </button> </div> </header> <div className="p-8 space-y-6"> <div className="flex flex-col sm:flex-row justify-between items-center mb-6 gap-4"> <div className="flex flex-wrap items-center gap-2 sm:gap-4"> <label htmlFor="sort-by" className="text-sm font-medium text-gray-700">SORT BY:</label> <select id="sort-by" value={sortKey} onChange={(e) => setSortKey(e.target.value)} className="px-3 py-1 rounded-md border-gray-300 shadow-sm border focus:ring-emerald-500 focus:border-emerald-500"> <option value="timestamp">Date Submitted</option> <option value="companyName">Company Name</option> </select> <label htmlFor="filter-by" className="text-sm font-medium text-gray-700">FILTER BY:</label> <select id="filter-by" value={filterAction} onChange={(e) => setFilterAction(e.target.value)} className="px-3 py-1 rounded-md border-gray-300 shadow-sm border focus:ring-emerald-500 focus:border-emerald-500"> <option value="all">All</option> <option value="ACCEPT">Accepted</option> <option value="REVISE">Revised</option> </select> </div> </div> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> {filteredResponses.length === 0 ? ( <div className="text-center py-10 text-gray-500 col-span-full">No responses match your criteria.</div> ) : ( filteredResponses.map(response => ( <div key={response.id} className="bg-white p-6 rounded-xl shadow-md border border-gray-100 flex flex-col justify-between h-full"> <div> <span className={`inline-block px-3 py-1 text-xs font-semibold rounded-full mb-4 ${response.action === 'ACCEPT' ? 'bg-emerald-200 text-emerald-800' : 'bg-blue-200 text-blue-800'}`}> {response.action === 'ACCEPT' ? 'ACCEPTED' : 'REVISION REQUESTED'} </span> <h3 className="text-lg font-bold text-gray-800 mb-2">{response.name} from {response.proposalDetails?.companyName || 'N/A'}</h3> <p className="text-sm text-gray-600"><strong>EMAIL:</strong> {response.email}</p> <p className="text-sm text-gray-600"><strong>LICENSE:</strong> {response.proposalDetails?.cultivatorLicense || 'N/A'}</p> {response.comments && <p className="text-sm text-gray-600 mt-2"><strong>COMMENTS:</strong> {response.comments}</p>} <ul className="list-disc list-inside mt-4 text-sm text-gray-600"> <li><strong>PRICE:</strong> {formatPrice(calculatePrice(response.proposalDetails))}</li> <li><strong>ENGAGEMENT:</strong> {response.proposalDetails?.engagement || 'N/A'} days</li> <li><strong>BATCHES:</strong> {response.proposalDetails?.totalBatches || 'N/A'}</li> <li><strong>UNITS:</strong> {response.proposalDetails?.totalUnits || 'N/A'}</li> </ul> </div> <div className="mt-4 pt-4 border-t border-gray-200 text-xs text-gray-400 flex justify-between items-center"> <div> <p>Submitted On: {response.timestamp?.toDate().toLocaleString()}</p> <p>User ID: <span className="font-mono">{response.userId}</span></p> </div> {response.proposalId && <button onClick={() => { setCurrentProposalId(response.proposalId); setCurrentView('client-proposal-view'); }} className="text-xs px-2 py-1 bg-emerald-500 text-white rounded-full hover:bg-emerald-600 transition"> VIEW PROPOSAL </button> } </div> </div> )) )} </div> </div> </div> ); }; const ClientProposalView = () => ( <div className="bg-slate-100 flex items-center justify-center min-h-screen p-4"> <div className="bg-white p-8 rounded-2xl shadow-xl border border-gray-200 w-full max-w-4xl mx-auto"> <div className="flex justify-between items-center mb-6"> <h1 className="text-3xl font-bold text-gray-800">CLIENT PROPOSAL</h1> <button onClick={() => setCurrentView('client-login')} className="px-4 py-2 bg-gray-200 text-gray-800 font-bold rounded-full shadow-lg hover:bg-gray-300"> ← BACK </button> </div> {isLoading ? ( <div className="text-center py-10 text-gray-500">LOADING PROPOSAL...</div> ) : ( clientProposalData ? ( <ProposalPreview proposal={clientProposalData} isClient={true} onAccept={() => openModal('ACCEPT')} onRevise={() => openModal('REVISE')} /> ) : ( <div className="text-center py-10 text-gray-500">PROPOSAL NOT FOUND. PLEASE CHECK YOUR CODE.</div> ) )} </div> {isModalOpen && <Modal />} </div> ); const ProposalPreview = ({ proposal, isClient, onAccept, onRevise }) => { const date = new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }); const price = calculatePrice(proposal); const calculatedPrice = (40000 * (((proposal.selectedCoreServices && proposal.selectedCoreServices.length) || 0) / 5)) + ((proposal.totalUnits || 0) * 1) + ((proposal.totalBatches || 0) * 10) + (((proposal.selectedAdditionalServices?.filter(s => s !== 'INSURANCE ASSESSMENT').length) || 0) * 8000); const startDate = proposal.engagementStartDate ? new Date(proposal.engagementStartDate) : new Date(); const endDate = new Date(startDate); endDate.setDate(endDate.getDate() + parseInt(proposal.engagement, 10)); const formattedStartDate = startDate.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }); const formattedEndDate = endDate.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }); return ( <div className="prose max-w-none text-sm p-4 rounded-xl shadow-inner bg-white min-h-[600px] overflow-auto"> <h1 className="text-xl font-bold mb-1">PROPOSAL FOR COMPREHENSIVE RECALL MANAGEMENT SERVICES</h1> <p className="text-sm text-gray-600 mb-0">PREPARED FOR: {proposal.companyName || 'CULTIVATOR COMPANY NAME'}</p> <p className="text-sm text-gray-600 mb-4">DATE: {date}</p> <p className="mb-4">WE AT CLEAN SLATE UNDERSTAND THE IMMENSE PRESSURE AND COMPLEXITY THAT A PUBLIC HEALTH AND SAFETY ADVISORY PLACES ON YOUR OPERATION. THE RECENT ADVISORY ISSUED BY THE CANNABIS CONTROL COMMISSION HAS PRESENTED A SIGNIFICANT CHALLENGE FOR YOUR TEAM, AND WE ARE HERE TO PROVIDE A CLEAR PATH FORWARD. OUR MISSION IS TO HELP YOU NAVIGATE THIS PROCESS EFFICIENTLY AND EFFECTIVELY, MOVING YOUR PRODUCT AND BUSINESS OUT OF RECALL PURGATORY.</p> <h2 className="text-lg font-bold mb-1">CULTIVATOR INFORMATION</h2> <ul className="list-disc list-inside mb-4"> <li>COMPANY NAME: {proposal.companyName || 'N/A'}</li> <li>LICENSE NUMBER: {proposal.cultivatorLicense || 'N/A'}</li> <li>ADDRESS: {proposal.cultivatorAddress || 'N/A'}</li> <li>CONTACT NAME: {proposal.cultivatorContactName || 'N/A'}</li> <li>CONTACT EMAIL: {proposal.cultivatorContactEmail || 'N/A'}</li> <li>CONTACT PHONE: {proposal.cultivatorContactPhone || 'N/A'}</li> </ul> <h2 className="text-lg font-bold mb-1">SELECTED SERVICES AND DELIVERABLES</h2> <h3 className="font-semibold text-gray-700 mb-1">CORE SERVICES</h3> <ul className="list-disc list-inside mb-4"> {(proposal.selectedCoreServices && proposal.selectedCoreServices.length) > 0 ? proposal.selectedCoreServices.map(s => <li key={s}>{s}</li>) : <li>NO CORE SERVICES SELECTED.</li>} </ul> <h3 className="font-semibold text-gray-700 mb-1">ADDITIONAL SERVICES</h3> <ul className="list-disc list-inside mb-4"> {(proposal.selectedAdditionalServices && proposal.selectedAdditionalServices.length) > 0 ? proposal.selectedAdditionalServices.map(s => <li key={s}>{s} {s === 'INSURANCE ASSESSMENT' ? '(FREE)' : ''}</li>) : <li>NO ADDITIONAL SERVICES SELECTED.</li>} </ul> <h2 className="text-lg font-bold mb-1">ENGAGEMENT DETAILS</h2> <ul className="list-disc list-inside mb-4"> <li>NUMBER OF BATCHES: {proposal.totalBatches || 'N/A'}</li> <li>TOTAL UNITS RECALLED: {proposal.totalUnits || 'N/A'}</li> <li>ENGAGEMENT LENGTH: {proposal.engagement || 'N/A'} days</li> <li>ENGAGEMENT START DATE: {formattedStartDate}</li> <li>ENGAGEMENT END DATE: {formattedEndDate}</li> <li>ESTIMATED FINANCIAL COMMITMENT: {proposal.overridePrice ? `<s>${formatPrice(calculatedPrice)}</s> <strong>${formatPrice(price)}</strong>` : `<strong>${formatPrice(price)}</strong>`}</li> </ul> <h2 className="text-lg font-bold mb-1">NEXT STEPS</h2> <p className="mb-4">WE WOULD WELCOME THE OPPORTUNITY TO DISCUSS YOUR SPECIFIC NEEDS AND CREATE A CUSTOMIZED PLAN TO HELP YOU NAVIGATE THIS RECALL. PLEASE CONTACT US TO SCHEDULE A BRIEF CONSULTATION.</p> <p className="font-bold">CLEAN SLATE</p> <p>{proposal.cleanSlateContactName || 'N/A'}</p> <p>{proposal.cleanSlateContactEmail || 'N/A'}</p> <p>{proposal.cleanSlateContactPhone || 'N/A'}</p> {isClient && ( <div className="mt-6 flex flex-col md:flex-row gap-4 justify-center"> <button onClick={onAccept} className="px-6 py-2 bg-emerald-600 text-white font-bold rounded-full shadow-lg hover:bg-emerald-700 transition"> ACCEPT PROPOSAL </button> <button onClick={onRevise} className="px-6 py-2 bg-gray-200 text-gray-800 font-bold rounded-full shadow-lg hover:bg-gray-300 transition"> REVISE PROPOSAL </button> </div> )} </div> ); }; const Modal = () => ( <div className="fixed inset-0 z-50 flex items-center justify-center transition-opacity duration-300 bg-black bg-opacity-50"> <div className="bg-white p-8 rounded-lg shadow-2xl max-w-md w-full m-4 border border-gray-200 transform translate-y-0 transition-transform duration-300"> <h3 className="text-xl font-bold mb-4 text-gray-800">{modalDetails.title}</h3> <p className="text-sm text-gray-600 mb-4">{modalDetails.message}</p> <form onSubmit={handleFormSubmit}> <div className="mb-4"> <label htmlFor="formName" className="block text-sm font-medium text-gray-700">YOUR NAME</label> <input type="text" id="formName" value={modalDetails.name} onChange={(e) => setModalDetails(prev => ({ ...prev, name: e.target.value }))} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm p-2 border focus:ring-emerald-500 focus:border-emerald-500" required /> </div> <div className="mb-4"> <label htmlFor="formEmail" className="block text-sm font-medium text-gray-700">YOUR EMAIL</label> <input type="email" id="formEmail" value={modalDetails.email} onChange={(e) => setModalDetails(prev => ({ ...prev, email: e.target.value }))} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm p-2 border focus:ring-emerald-500 focus:border-emerald-500" required /> </div> {modalAction === 'REVISE' && ( <div className="mb-4"> <label htmlFor="formComments" className="block text-sm font-medium text-gray-700">COMMENTS / REVISION NOTES</label> <textarea id="formComments" rows="3" value={modalDetails.comments} onChange={(e) => setModalDetails(prev => ({ ...prev, comments: e.target.value }))} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm p-2 border focus:ring-emerald-500 focus:border-emerald-500"></textarea> </div> )} {modalDetails.submissionStatus && <p className="text-sm my-2 text-center text-emerald-600 font-semibold">{modalDetails.submissionStatus}</p>} <div className="flex justify-end gap-4 mt-4"> <button type="button" onClick={() => setIsModalOpen(false)} className="px-6 py-2 bg-gray-200 text-gray-800 font-bold rounded-full shadow-lg hover:bg-gray-300"> CANCEL </button> <button type="submit" className="px-6 py-2 bg-emerald-600 text-white font-bold rounded-full shadow-lg hover:bg-emerald-700 disabled:bg-gray-400"> SUBMIT </button> </div> </form> </div> </div> ); // --- Main Rendering Switch --- if (loading) { return ( <div className="flex items-center justify-center min-h-screen"> <div className="text-center text-gray-500">Loading application...</div> </div> ); } switch (currentView) { case 'client-login': return <ClientLoginView />; case 'admin-login': return <AdminLoginView />; case 'generator': return <AdminGeneratorView />; case 'dashboard': return <AdminDashboardView />; case 'client-proposal-view': return <ClientProposalView />; default: return <div className="text-center text-gray-500 p-8">Page not found.</div>; } }; export default App;