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;