import React, { useState, useEffect } from "react";
import { base44 } from "@/api/base44Client";
import { useMutation, useQuery } from "@tanstack/react-query";
import { useNavigate } from "react-router-dom";
import { createPageUrl } from "@/utils";
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { Badge } from "@/components/ui/badge";
import { Progress } from "@/components/ui/progress";
import {
Building2, FileText, MapPin, DollarSign, Sparkles, Upload,
CheckCircle2, Loader2, AlertCircle, ArrowRight, ArrowLeft,
Shield, TrendingUp, Users, Briefcase, Mail, Phone, Hash,
Search, Calendar, Plus, Trash2, Check, Target
} from "lucide-react";
import { useToast } from "@/components/ui/use-toast";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Checkbox } from "@/components/ui/checkbox";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
} from "@/components/ui/dialog";
import DragDropFileUpload from "@/components/common/DragDropFileUpload";
import DocumentViewer from "@/components/vendor/DocumentViewer";
// Google Places Autocomplete Component
const GoogleAddressInput = ({ value, onChange, placeholder, label, required }) => {
const [inputValue, setInputValue] = useState(value || "");
const [suggestions, setSuggestions] = useState([]);
const [showSuggestions, setShowSuggestions] = useState(false);
const inputRef = React.useRef(null);
useEffect(() => {
setInputValue(value || "");
}, [value]);
const handleAddressSearch = async (searchText) => {
setInputValue(searchText);
if (searchText.length < 3) {
setSuggestions([]);
return;
}
try {
// Use Google Places API via integration
const response = await base44.integrations.Core.InvokeLLM({
prompt: `Extract and return ONLY a valid JSON array of 5 US address suggestions for: "${searchText}". Format each as: {"address": "full formatted address", "city": "city", "state": "state", "zip": "zip"}. Return ONLY the JSON array, no other text.`,
response_json_schema: {
type: "object",
properties: {
suggestions: {
type: "array",
items: {
type: "object",
properties: {
address: { type: "string" },
city: { type: "string" },
state: { type: "string" },
zip: { type: "string" }
}
}
}
}
}
});
if (response?.suggestions) {
setSuggestions(response.suggestions);
setShowSuggestions(true);
}
} catch (error) {
console.error("Address search error:", error);
}
};
const handleSelectAddress = (suggestion) => {
const fullAddress = `${suggestion.address}, ${suggestion.city}, ${suggestion.state} ${suggestion.zip}`;
setInputValue(fullAddress);
onChange(fullAddress);
setSuggestions([]);
setShowSuggestions(false);
};
return (
{label} {required && * }
{
handleAddressSearch(e.target.value);
}}
onBlur={() => {
// Delay to allow click on suggestion
setTimeout(() => setShowSuggestions(false), 200);
}}
onFocus={() => {
if (suggestions.length > 0) setShowSuggestions(true);
}}
placeholder={placeholder}
className="pl-10"
/>
{showSuggestions && suggestions.length > 0 && (
{suggestions.map((suggestion, idx) => (
handleSelectAddress(suggestion)}
className="w-full text-left px-4 py-3 hover:bg-slate-50 border-b border-slate-100 last:border-0 transition-colors"
>
{suggestion.address}
{suggestion.city}, {suggestion.state} {suggestion.zip}
))}
)}
);
};
export default function SmartVendorOnboarding() {
const { toast } = useToast();
const navigate = useNavigate();
const [currentStep, setCurrentStep] = useState(0); // Changed from 1 to 0 for welcome screen
const [generatingRates, setGeneratingRates] = useState(false);
// Function to detect minimum wage based on business address
const getMinimumWageFromAddress = (address) => {
if (!address) return 18; // Default fallback
const addr = address.toLowerCase();
// State-specific minimum wages (as of 2024)
if (addr.includes('california') || addr.includes(' ca ') || addr.includes('ca,')) return 16.00;
if (addr.includes('washington') || addr.includes(' wa ')) return 16.28;
if (addr.includes('massachusetts') || addr.includes(' ma ')) return 15.00;
if (addr.includes('new york') || addr.includes(' ny ')) return 15.00;
if (addr.includes('connecticut') || addr.includes(' ct ')) return 15.00;
if (addr.includes('arizona') || addr.includes(' az ')) return 14.35;
if (addr.includes('colorado') || addr.includes(' co ')) return 14.42;
if (addr.includes('maine') || addr.includes(' me ')) return 14.15;
if (addr.includes('oregon') || addr.includes(' or ')) return 14.20;
if (addr.includes('vermont') || addr.includes(' vt ')) return 13.67;
if (addr.includes('illinois') || addr.includes(' il ')) return 14.00;
if (addr.includes('rhode island') || addr.includes(' ri ')) return 14.00;
if (addr.includes('new jersey') || addr.includes(' nj ')) return 15.13;
if (addr.includes('florida') || addr.includes(' fl ')) return 12.00;
if (addr.includes('nevada') || addr.includes(' nv ')) return 12.00;
if (addr.includes('hawaii') || addr.includes(' hi ')) return 14.00;
if (addr.includes('texas') || addr.includes(' tx ')) return 7.25;
if (addr.includes('georgia') || addr.includes(' ga ')) return 7.25;
if (addr.includes('pennsylvania') || addr.includes(' pa ')) return 7.25;
if (addr.includes('ohio') || addr.includes(' oh ')) return 10.45;
if (addr.includes('michigan') || addr.includes(' mi ')) return 10.33;
if (addr.includes('virginia') || addr.includes(' va ')) return 12.00;
if (addr.includes('north carolina') || addr.includes(' nc ')) return 7.25;
if (addr.includes('tennessee') || addr.includes(' tn ')) return 7.25;
return 15.00; // Default minimum wage for unspecified states
};
const [formData, setFormData] = useState({
// NEW: NDA Step (Step 1)
nda_acknowledged: false,
nda_signed_by: "",
nda_signature_date: "",
nda_signature_time: "",
nda_signature_image: "", // NEW: Store the signature image data
// Contract Data (now Step 2)
contract_review_notes: "",
contract_acknowledged: false,
contract_va_fee_acknowledged: false,
contract_review_time: 0,
// Business Identity (now Step 3)
legal_name: "",
dba: "",
tax_id: "",
business_type: "",
primary_contact_name: "",
primary_contact_email: "",
primary_contact_phone: "",
billing_address: "",
service_address: "",
same_as_billing: false, // This now means "billing address is same as service address"
total_employees: "",
has_software: "",
software_name: "",
software_type: "",
// Documents (now Step 4)
w9_file: null,
coi_file: null,
sos_file: null,
w9_url: "",
coi_url: "",
sos_url: "",
background_check_attestation: false,
i9_compliance_attestation: false,
legal_compliance_attestation: false,
// Service Coverage (now Step 5)
coverage_regions: [],
selected_states: [],
selected_cities: {},
// Rate Proposals (now Step 6)
rate_proposals: [],
default_location: ""
});
const [uploadingDoc, setUploadingDoc] = useState(null);
const [validatingDoc, setValidatingDoc] = useState(null);
const [docValidation, setDocValidation] = useState({
w9: null,
coi: null,
sos: null,
// contract: null, // Removed for contract upload status
});
const [showAIInsights, setShowAIInsights] = useState(false);
const [aiInsights, setAiInsights] = useState(null);
// Get invite data from URL
const urlParams = new URLSearchParams(window.location.search);
const inviteCode = urlParams.get('invite');
const { data: invite } = useQuery({
queryKey: ['vendor-invite', inviteCode],
queryFn: async () => {
if (!inviteCode) return null;
const invites = await base44.entities.VendorInvite.filter({ invite_code: inviteCode });
return invites[0];
},
enabled: !!inviteCode
});
// Pre-fill data from invite
useEffect(() => {
if (invite) {
setFormData(prev => ({
...prev,
legal_name: invite.company_name || "",
primary_contact_name: invite.primary_contact_name || "",
primary_contact_email: invite.primary_contact_email || ""
}));
}
}, [invite]);
const steps = [
{ number: 0, title: "Welcome", icon: Sparkles, subtitle: "Let's get started" },
{ number: 1, title: "NDA & Trust", icon: Shield, subtitle: "Sign and accept agreement" },
{ number: 2, title: "Foodbuy Contract", icon: FileText, subtitle: "Review & acknowledge terms" },
{ number: 3, title: "Business Identity", icon: Building2, subtitle: "Let's set your stage" },
{ number: 4, title: "Documents & Validation", icon: Shield, subtitle: "Compliance essentials" },
{ number: 5, title: "Service Coverage", icon: MapPin, subtitle: "Where you operate" },
{ number: 6, title: "Rate Proposals", icon: DollarSign, subtitle: "Competitive pricing" },
{ number: 7, title: "AI Intelligence", icon: Sparkles, subtitle: "Market insights" }
];
const progressPercentage = currentStep === 0 ? 0 : ((currentStep - 1) / (steps.length - 2)) * 100;
// Upload document
const handleFileUpload = async (file, docType) => {
if (!file) return;
setUploadingDoc(docType);
try {
// Upload file
const { file_url } = await base44.integrations.Core.UploadFile({ file });
setFormData(prev => ({
...prev,
[`${docType}_url`]: file_url,
[`${docType}_file`]: file
}));
// For contract, the logic has been removed as it's handled by DocumentViewer
// and AI analysis of an uploaded contract is no longer directly here.
// Validate document with AI
setValidatingDoc(docType);
const validation = await validateDocument(file_url, docType);
setDocValidation(prev => ({
...prev,
[docType]: validation
}));
toast({
title: "Document Uploaded",
description: validation.isValid ? "✅ Document validated successfully" : "⚠️ Please review validation notes",
});
} catch (error) {
toast({
title: "Upload Failed",
description: error.message,
variant: "destructive"
});
} finally {
setUploadingDoc(null);
setValidatingDoc(null);
}
};
// Validate document with AI
const validateDocument = async (fileUrl, docType) => {
const prompts = {
w9: "Analyze this W-9 tax form and verify: 1) Business name is filled 2) Tax ID is present 3) Business entity type is checked 4) Signature is present. Return validation results.",
coi: "Analyze this Certificate of Insurance and verify: 1) General liability coverage is present 2) Coverage amount meets $1M minimum 3) Policy is not expired 4) KROW or client is listed as additional insured. Return validation results.",
sos: "Analyze this Secretary of State certificate and verify: 1) Business is actively registered 2) Registration is current/not expired 3) Business name matches application 4) State of registration. Return validation results."
};
try {
const analysis = await base44.integrations.Core.InvokeLLM({
prompt: prompts[docType],
file_urls: [fileUrl],
response_json_schema: {
type: "object",
properties: {
isValid: { type: "boolean" },
businessName: { type: "string" },
expiryDate: { type: "string" },
registrationState: { type: "string" },
issues: { type: "array", items: { type: "string" } },
notes: { type: "string" }
}
}
});
return analysis;
} catch (error) {
return {
isValid: false,
issues: ["Unable to validate document automatically"],
notes: "Manual review required"
};
}
};
// Generate AI rate suggestions - This function remains but its direct UI trigger in Step 4 is removed
const generateAIRates = async () => {
setGeneratingRates(true);
try {
const response = await base44.integrations.Core.InvokeLLM({
prompt: `
Generate competitive rate proposals for ${formData.legal_name}.
Business Type: ${formData.business_type}
Service Regions: ${Object.values(formData.selected_cities).flat().map(city => city).join(", ")}
Total Employees: ${formData.total_employees}
Software Type: ${formData.software_type}
Provide 10-15 common staffing roles with competitive rates for the hospitality/event industry.
Consider regional market rates and vendor's operational efficiency.
For each role, provide:
- Role name
- Category (Kitchen and Culinary, Concessions, Facilities, Bartending, Security, Event Staff, Management)
- Employee wage (competitive market rate)
- Suggested markup percentage (18-25% based on role complexity)
- Calculated client rate
`,
add_context_from_internet: true,
response_json_schema: {
type: "object",
properties: {
roles: {
type: "array",
items: {
type: "object",
properties: {
role_name: { type: "string" },
category: { type: "string" },
employee_wage: { type: "number" },
markup_percentage: { type: "number" },
client_rate: { type: "number" },
market_comparison: { type: "string" }
}
}
}
}
}
});
if (response?.roles) {
setFormData(prev => ({
...prev,
rate_proposals: response.roles
}));
toast({
title: "Rates Generated",
description: `${response.roles.length} competitive rates suggested`,
});
}
} catch (error) {
toast({
title: "Generation Failed",
description: error.message,
variant: "destructive"
});
} finally {
setGeneratingRates(null);
}
};
// Generate AI insights
const generateAIInsights = async () => {
try {
const insights = await base44.integrations.Core.InvokeLLM({
prompt: `
Analyze this vendor application and provide strategic insights:
Vendor: ${formData.legal_name}
Business Type: ${formData.business_type}
Total Employees: ${formData.total_employees}
Software: ${formData.software_name || formData.software_type}
Service Areas: ${Object.values(formData.selected_cities).flat().map(city => city).join(", ")}
Rate Proposals: ${formData.rate_proposals.length} roles
Provide:
1. Competitive positioning (1-5 stars)
2. Market alignment assessment
3. Pricing competitiveness
4. Operational readiness score
5. Recommended focus areas
6. Potential challenges
`,
add_context_from_internet: true,
response_json_schema: {
type: "object",
properties: {
overall_score: { type: "number" },
competitive_position: { type: "string" },
market_alignment: { type: "string" },
pricing_analysis: { type: "string" },
operational_readiness: { type: "string" },
strengths: { type: "array", items: { type: "string" } },
recommendations: { type: "array", items: { type: "string" } },
potential_challenges: { type: "array", items: { type: "string" } }
}
}
});
setAiInsights(insights);
setShowAIInsights(true);
} catch (error) {
toast({
title: "Analysis Failed",
description: error.message,
variant: "destructive"
});
}
};
// US States and Major Cities Data
const usLocationData = {
"Alabama": ["Birmingham", "Montgomery", "Mobile", "Huntsville"],
"Alaska": ["Anchorage", "Fairbanks", "Juneau"],
"Arizona": ["Phoenix", "Tucson", "Mesa", "Scottsdale", "Chandler"],
"Arkansas": ["Little Rock", "Fort Smith", "Fayetteville"],
"California": ["Los Angeles", "San Francisco", "San Diego", "San Jose", "Sacramento", "Oakland", "Fresno", "Long Beach", "Anaheim", "Bakersfield"],
"Colorado": ["Denver", "Colorado Springs", "Aurora", "Boulder", "Fort Collins"],
"Connecticut": ["Hartford", "New Haven", "Stamford", "Bridgeport"],
"Delaware": ["Wilmington", "Dover", "Newark"],
"Florida": ["Miami", "Orlando", "Tampa", "Jacksonville", "Fort Lauderdale", "West Palm Beach"],
"Georgia": ["Atlanta", "Savannah", "Augusta", "Columbus", "Macon"],
"Hawaii": ["Honolulu", "Hilo", "Kailua"],
"Idaho": ["Boise", "Meridian", "Nampa"],
"Illinois": ["Chicago", "Aurora", "Naperville", "Rockford", "Joliet"],
"Indiana": ["Indianapolis", "Fort Wayne", "Evansville", "South Bend"],
"Iowa": ["Des Moines", "Cedar Rapids", "Davenport"],
"Kansas": ["Wichita", "Overland Park", "Kansas City", "Topeka"],
"Kentucky": ["Louisville", "Lexington", "Bowling Green"],
"Louisiana": ["New Orleans", "Baton Rouge", "Shreveport", "Lafayette"],
"Maine": ["Portland", "Lewiston", "Bangor"],
"Maryland": ["Baltimore", "Columbia", "Germantown", "Silver Spring"],
"Massachusetts": ["Boston", "Worcester", "Springfield", "Cambridge", "Lowell"],
"Michigan": ["Detroit", "Grand Rapids", "Warren", "Sterling Heights", "Ann Arbor"],
"Minnesota": ["Minneapolis", "St. Paul", "Rochester", "Duluth"],
"Mississippi": ["Jackson", "Gulfport", "Southaven"],
"Missouri": ["Kansas City", "St. Louis", "Springfield", "Columbia"],
"Montana": ["Billings", "Missoula", "Great Falls"],
"Nebraska": ["Omaha", "Lincoln", "Bellevue"],
"Nevada": ["Las Vegas", "Henderson", "Reno", "North Las Vegas"],
"New Hampshire": ["Manchester", "Nashua", "Concord"],
"New Jersey": ["Newark", "Jersey City", "Paterson", "Elizabeth", "Edison"],
"New Mexico": ["Albuquerque", "Las Cruces", "Rio Rancho", "Santa Fe"],
"New York": ["New York City", "Buffalo", "Rochester", "Yonkers", "Syracuse", "Albany"],
"North Carolina": ["Charlotte", "Raleigh", "Greensboro", "Durham", "Winston-Salem"],
"North Dakota": ["Fargo", "Bismarck", "Grand Forks"],
"Ohio": ["Columbus", "Cleveland", "Cincinnati", "Toledo", "Akron"],
"Oklahoma": ["Oklahoma City", "Tulsa", "Norman"],
"Oregon": ["Portland", "Salem", "Eugene", "Gresham"],
"Pennsylvania": ["Philadelphia", "Pittsburgh", "Allentown", "Erie", "Reading"],
"Rhode Island": ["Providence", "Warwick", "Cranston"],
"South Carolina": ["Charleston", "Columbia", "North Charleston", "Mount Pleasant"],
"South Dakota": ["Sioux Falls", "Rapid City", "Aberdeen"],
"Tennessee": ["Nashville", "Memphis", "Knoxville", "Chattanooga"],
"Texas": ["Houston", "San Antonio", "Dallas", "Austin", "Fort Worth", "El Paso", "Arlington", "Corpus Christi"],
"Utah": ["Salt Lake City", "West Valley City", "Provo", "West Jordan"],
"Vermont": ["Burlington", "South Burlington", "Rutland"],
"Virginia": ["Virginia Beach", "Norfolk", "Chesapeake", "Richmond", "Newport News"],
"Washington": ["Seattle", "Spokane", "Tacoma", "Vancouver", "Bellevue"],
"West Virginia": ["Charleston", "Huntington", "Morgantown"],
"Wisconsin": ["Milwaukee", "Madison", "Green Bay", "Kenosha"],
"Wyoming": ["Cheyenne", "Casper", "Laramie"]
};
// Submit vendor application - UPDATED
const submitApplicationMutation = useMutation({
mutationFn: async () => {
// Generate vendor number
const vendorNumber = `VN-${Math.floor(1000 + Math.random() * 9000)}`;
// Build coverage regions from selected cities
const coverageRegionsList = [];
Object.keys(formData.selected_cities).forEach(state => {
formData.selected_cities[state].forEach(city => {
coverageRegionsList.push(`${city}, ${state}`);
});
});
// Create vendor record
const vendor = await base44.entities.Vendor.create({
vendor_number: vendorNumber,
legal_name: formData.legal_name,
doing_business_as: formData.dba,
tax_id: formData.tax_id,
business_type: formData.business_type,
primary_contact_name: formData.primary_contact_name,
primary_contact_email: formData.primary_contact_email,
primary_contact_phone: formData.primary_contact_phone,
// Updated logic for billing_address and service_address based on new flow
service_address: formData.service_address,
billing_address: formData.same_as_billing ? formData.service_address : formData.billing_address,
coverage_regions: coverageRegionsList,
w9_document: formData.w9_url,
coi_document: formData.coi_url,
insurance_certificate: formData.coi_url,
approval_status: "pending",
is_active: false,
// Updated contract note to reflect review not upload
notes: `Total Employees: ${formData.total_employees}, Software: ${formData.software_name || formData.software_type}, SOS: ${formData.sos_url ? 'Verified' : 'Pending'}, Contract: ${formData.contract_acknowledged ? 'Acknowledged' : 'Not Acknowledged'}. Contract Review Notes: ${formData.contract_review_notes}. NDA Signed: ${formData.nda_acknowledged ? 'Yes' : 'No'}`
});
// Create rate proposals with location-specific rates
const vendorFee = invite?.vendor_admin_fee || 12;
await Promise.all(
formData.rate_proposals
.filter(rate => rate.is_active)
.map(rate =>
base44.entities.VendorRate.create({
vendor_id: vendor.id,
vendor_name: formData.legal_name,
category: rate.category,
role_name: rate.role_name,
employee_wage: rate.employee_wage,
markup_percentage: rate.markup_percentage,
vendor_fee_percentage: vendorFee,
client_rate: rate.client_rate,
is_active: true,
ai_analysis: rate.ai_analysis,
is_custom: rate.is_custom,
notes: rate.location_rates ? JSON.stringify(rate.location_rates) : null
})
)
);
// Update invite status if exists
if (invite) {
await base44.entities.VendorInvite.update(invite.id, {
invite_status: "completed",
vendor_id: vendor.id,
invite_accepted_date: new Date().toISOString()
});
}
// Send confirmation email
await base44.integrations.Core.SendEmail({
from_name: "KROW Platform",
to: formData.primary_contact_email,
subject: "Vendor Application Received - Under Review",
body: `
Application Received Successfully!
Dear ${formData.primary_contact_name},
Thank you for completing your vendor application with KROW.
Application Summary:
Vendor Number: ${vendorNumber}
Company: ${formData.legal_name}
Status: Under Review
Our procurement team will review your application and get back to you within 2-3 business days.
Best regards, KROW Team
`
});
return vendor;
},
onSuccess: (vendor) => {
toast({
title: "Application Submitted!",
description: "You'll hear from us within 2-3 business days",
});
navigate(createPageUrl("VendorManagement"));
},
onError: (error) => {
toast({
title: "Submission Failed",
description: error.message,
variant: "destructive"
});
}
});
const handleNext = () => {
// Step 0: Welcome (no validation, just go to next step)
if (currentStep === 0) {
setCurrentStep(1);
return;
}
// Step 1: NDA
if (currentStep === 1) {
if (!formData.nda_acknowledged) {
toast({
title: "NDA Required",
description: "You must sign and accept the NDA before proceeding",
variant: "destructive"
});
return;
}
}
// Step 2: Foodbuy Contract
if (currentStep === 2) {
if (!formData.contract_acknowledged || !formData.contract_va_fee_acknowledged) {
toast({
title: "Contract Review Required",
description: "Please acknowledge all terms to proceed",
variant: "destructive"
});
return;
}
}
// Step 3: Business Identity
if (currentStep === 3) {
if (!formData.legal_name || !formData.tax_id || !formData.business_type ||
!formData.primary_contact_name || !formData.primary_contact_email ||
!formData.service_address || !formData.total_employees ||
(formData.has_software === "yes" && (!formData.software_type || !formData.software_name))) {
toast({
title: "Missing Information",
description: "Please fill in all required fields",
variant: "destructive"
});
return;
}
}
// Step 4: Documents
if (currentStep === 4) {
const isSosValidated = (docValidation.sos?.autoVerified && docValidation.sos.isRegistered && (docValidation.sos.registrationStatus === 'Active' || docValidation.sos.registrationStatus === 'Good Standing')) || (docValidation.sos && docValidation.sos.isValid);
if (!formData.w9_url || !formData.coi_url || !isSosValidated) {
toast({
title: "Missing Documents",
description: "Please upload W-9, COI, and verify/upload Secretary of State certificate",
variant: "destructive"
});
return;
}
// NEW: Validate compliance attestations
if (!formData.background_check_attestation || !formData.i9_compliance_attestation || !formData.legal_compliance_attestation) {
toast({
title: "Compliance Attestation Required",
description: "Please confirm all compliance attestations to proceed",
variant: "destructive"
});
return;
}
}
// Step 5: Service Coverage
if (currentStep === 5) {
const totalCities = Object.values(formData.selected_cities).reduce((sum, cities) => sum + cities.length, 0);
if (totalCities === 0) {
toast({
title: "No Coverage Selected",
description: "Please select at least one city/location you can service",
variant: "destructive"
});
return;
}
}
// Step 6: Rate Proposals
if (currentStep === 6) {
const activeRates = formData.rate_proposals.filter(rate => rate.is_active);
if (activeRates.length === 0) {
toast({
title: "No Active Rate Proposals",
description: "Please activate at least one rate proposal.",
variant: "destructive"
});
return;
}
const hasInvalidRates = activeRates.some(rate =>
!rate.role_name || !rate.category || rate.employee_wage <= 0 || rate.markup_percentage <= 0 || rate.client_rate <= 0
);
if (hasInvalidRates) {
toast({
title: "Incomplete Rate Proposals",
description: "Please ensure all active rate proposals have valid employee wages and markups.",
variant: "destructive"
});
return;
}
}
if (currentStep < steps.length - 1) {
setCurrentStep(prev => prev + 1);
}
};
const handleBack = () => {
if (currentStep > 0) {
setCurrentStep(prev => prev - 1);
}
};
const handleSubmit = () => {
submitApplicationMutation.mutate();
};
// Update handleSignNDA to handle signature data from DocumentViewer
const handleSignNDA = (acknowledgmentData) => {
const now = new Date();
setFormData(prev => ({
...prev,
nda_acknowledged: true,
nda_signed_by: acknowledgmentData.signerName || invite?.primary_contact_name || prev.primary_contact_name || "Vendor",
nda_signature_date: now.toLocaleDateString(),
nda_signature_time: now.toLocaleTimeString(),
nda_signature_image: acknowledgmentData.signature || "" // Store the signature image
}));
toast({
title: "✅ NDA Signed",
description: `Signed on ${now.toLocaleString()}`,
});
};
return (
{/* STEP 0: Welcome Screen */}
{currentStep === 0 && (
{/* Logo - KROW LOGO */}
{/* Welcome Message */}
Welcome to KROW
The Workforce Control Tower that connects you to the most trusted procurement and operator network.
Let's begin your onboarding journey, it'll take just a few minutes to get your company fully connected.
{/* Progress indicator */}
Step 1 of 8: NDA & Trust Agreement
{/* Start Button */}
Start Onboarding
{invite && (
✅ Invited by {invite.invited_by}
)}
)}
{/* Steps 1-7: Show header and progress only if not on welcome screen */}
{currentStep > 0 && (
<>
{/* Header */}
Smart Vendor Onboarding
Real-time market analysis • Predictive scoring • Instant validation
{invite && (
✅ Invited by {invite.invited_by}
)}
{/* Progress Bar */}
Step {currentStep} of {steps.length - 1}
{Math.round(progressPercentage)}% Complete
{/* Step indicators */}
{steps.slice(1).map((step) => {
const Icon = step.icon;
const isActive = currentStep === step.number;
const isComplete = currentStep > step.number;
return (
setCurrentStep(step.number)}
className={`p-3 rounded-lg text-center transition-all ${
isActive
? 'bg-[#0A39DF] text-white shadow-lg scale-105'
: isComplete
? 'bg-green-100 text-green-700'
: 'bg-slate-100 text-slate-400'
}`}
>
{step.title}
);
})}
>
)}
{/* STEP 1: NDA & Trust Agreement */}
{currentStep === 1 && (
NDA & Trust Agreement
Sign and accept - Date and time will be recorded
{/* NDA Document Viewer */}
{/* Warning if not signed */}
{!formData.nda_acknowledged && (
You must review and sign the NDA to proceed with onboarding
)}
{/* Success message if signed - NOW WITH SIGNATURE IMAGE */}
{formData.nda_acknowledged && (
✅ NDA Signed Successfully
{/* Signature and Details Card */}
{/* Signer Name */}
Signed by:
{formData.nda_signed_by}
{/* Signature Image */}
{formData.nda_signature_image && (
Digital Signature:
)}
{/* Date and Time */}
Date:
{formData.nda_signature_date}
Time:
{formData.nda_signature_time}
✓ You may now proceed to the next step
)}
)}
{/* STEP 2: Foodbuy Contract Review (was Step 1) */}
{currentStep === 2 && (
Foodbuy Contract Review
Please review the terms before proceeding
{/* Contract Document Viewer */}
{
setFormData(prev => ({
...prev,
contract_review_notes: acknowledgmentData.notes || prev.contract_review_notes,
contract_review_time: acknowledgmentData.reviewTime || prev.contract_review_time,
contract_acknowledged: true
}));
}}
initialNotes={formData.contract_review_notes}
isAcknowledged={formData.contract_acknowledged}
timeSpent={formData.contract_review_time}
/>
{/* Performance Requirements & VA Fee Structure Breakdown */}
Performance Requirements & Service Standards
{/* Fill Rate & KPIs */}
Key Performance Indicators (KPIs)
Fill Rate Target
≥ 95%
On-Time Arrival
≥ 98%
Client Satisfaction (CSAT)
≥ 4.5/5.0
Cancellation Rate
≤ 3%
No-Show Rate
≤ 1%
Response Time
≤ 2 hours
{/* Compliance & Attestations */}
Compliance & Attestation Requirements
Background Check Compliance
All employees must have valid background checks per federal and state requirements
I-9 Employment Eligibility
Maintain complete and valid I-9 forms for all employees with proper documentation
Insurance & Liability Coverage
General liability insurance minimum $1M, Workers' Compensation as required by law
Legal & Regulatory Compliance
Comply with all federal, state, and local employment laws including wage & hour, safety, and anti-discrimination
Tax & Payroll Compliance
Proper W-9 on file, accurate tax withholding, and timely payment of all employment taxes
{/* VA Fee Structure */}
Vendor Fee Structure
Vendor Fee
{invite?.vendor_admin_fee || 12}%
Cost of doing business with Foodbuy as your partner
Fee Calculation Example
Employee Wage
$18.50/hr
Your Markup (20%)
+$3.70/hr
Bill Rate (to client)
$22.20/hr
Vendor Fee ({invite?.vendor_admin_fee || 12}%)
-${((22.20 * (invite?.vendor_admin_fee || 12)) / 100).toFixed(2)}/hr
You Receive
${(22.20 - (22.20 * (invite?.vendor_admin_fee || 12) / 100)).toFixed(2)}/hr
💡 What the Vendor Fee Covers: Platform access, procurement management, client relationships, compliance oversight, payment processing, insurance coordination, and KROW system support.
{/* Acknowledgment Checkbox */}
setFormData(prev => ({ ...prev, contract_va_fee_acknowledged: checked }))}
className="mt-1 data-[state=checked]:bg-[#0A39DF] data-[state=checked]:border-[#0A39DF]"
/>
I understand the performance requirements and vendor fee structure *
Including the {invite?.vendor_admin_fee || 12}% vendor fee and service standards outlined above
{(!formData.contract_acknowledged || !formData.contract_va_fee_acknowledged) && (
You must review and acknowledge the contract terms to proceed with onboarding
)}
)}
{/* STEP 3: Business Identity (was Step 2) */}
{currentStep === 3 && (
Business Identity
Let's set your stage
{/* Legal Business Name */}
{/* Tax ID & Business Type */}
{/* Contact Information */}
{/* Google Address Autocomplete - SERVICE ADDRESS FIRST */}
setFormData(prev => ({ ...prev, service_address: value }))}
placeholder="123 Main Street, Suite 100, San Francisco, CA 94105"
label="Service Address"
required
/>
{/* Same as service checkbox */}
setFormData(prev => ({ ...prev, same_as_billing: checked }))}
className="data-[state=checked]:bg-[#0A39DF] data-[state=checked]:border-[#0A39DF]"
/>
Billing address is same as service address
{/* Billing Address - only show if checkbox is unchecked */}
{!formData.same_as_billing && (
setFormData(prev => ({ ...prev, billing_address: value }))}
placeholder="456 Office Blvd, Oakland, CA 94612"
label="Billing Address"
/>
)}
{/* NEW FIELDS: Staff Count & Software */}
Operations Information
{/* Total Employees */}
{/* Has Software */}
Do you use operational software? *
{
setFormData(prev => ({
...prev,
has_software: value,
software_name: value === "no" ? "traditional" : ""
}));
}}>
Yes, we have software
No, traditional operations
{/* Software Details */}
{formData.has_software === "yes" && (
)}
)}
{/* STEP 4: Documents & Validation (was Step 3) */}
{currentStep === 4 && (
Documents & Validation
Compliance essentials - AI powered verification
{/* W-9 Upload */}
W-9 Tax Form *
Federal tax classification document
{docValidation.w9 && (
{docValidation.w9.isValid ? "✅ Validated" : "ℹ Review"}
)}
handleFileUpload(file, 'w9')}
accept=".pdf,.jpg,.jpeg,.png"
uploading={uploadingDoc === 'w9' || validatingDoc === 'w9'}
uploaded={!!formData.w9_url}
uploadedFileName={formData.w9_file?.name}
disabled={uploadingDoc === 'w9' || validatingDoc === 'w9'}
/>
{docValidation.w9 && (
Validation Results:
{docValidation.w9.businessName && (
• Business: {docValidation.w9.businessName}
)}
{docValidation.w9.issues && docValidation.w9.issues.length > 0 && (
{docValidation.w9.issues.map((issue, idx) => (
ℹ {issue}
))}
)}
{docValidation.w9.notes && (
{docValidation.w9.notes}
)}
)}
{/* COI Upload */}
Certificate of Insurance (COI) *
General liability insurance certificate
{docValidation.coi && (
{docValidation.coi.isValid ? "✅ Validated" : "ℹ Review"}
)}
handleFileUpload(file, 'coi')}
accept=".pdf,.jpg,.jpeg,.png"
uploading={uploadingDoc === 'coi' || validatingDoc === 'coi'}
uploaded={!!formData.coi_url}
uploadedFileName={formData.coi_file?.name}
disabled={uploadingDoc === 'coi' || validatingDoc === 'coi'}
/>
{docValidation.coi && (
Validation Results:
{docValidation.coi.expiryDate && (
• Expires: {docValidation.coi.expiryDate}
)}
{docValidation.coi.issues && docValidation.coi.issues.length > 0 && (
{docValidation.coi.issues.map((issue, idx) => (
ℹ {issue}
))}
)}
{docValidation.coi.notes && (
{docValidation.coi.notes}
)}
)}
{/* Secretary of State Certificate */}
Secretary of State Certificate *
Business registration verification - Required
{docValidation.sos && (
{(docValidation.sos.isValid || (docValidation.sos.isRegistered && docValidation.sos.autoVerified)) ? "✅ Validated" : "ℹ Review"}
)}
{/* Auto-Check Button - NOW REQUIRED */}
🔍 Automatic Verification - Required
Click "Auto-Check" to verify your business registration with the Secretary of State. This is mandatory for approval.
{
if (!formData.legal_name || !formData.service_address) { // Changed to service_address
toast({
title: "Missing Information",
description: "Please fill in business name and service address first",
variant: "destructive"
});
return;
}
setValidatingDoc('sos');
try {
// Extract state from service address (using service_address now)
const addressParts = formData.service_address.split(',');
const stateZip = addressParts[addressParts.length - 1]?.trim() || '';
const state = stateZip.split(' ')[0];
const validation = await base44.integrations.Core.InvokeLLM({
prompt: `
Search public Secretary of State records for this business:
Business Name: ${formData.legal_name}
State: ${state}
Tax ID: ${formData.tax_id || 'Not provided'}
Verify:
1. Is this business actively registered with the Secretary of State?
2. What is the registration status (Active, Inactive, Suspended)?
3. What is the business entity type on file?
4. When was it registered?
5. Is the registration current and in good standing?
Use public Secretary of State databases and business registries.
Return accurate verification results.
`,
add_context_from_internet: true,
response_json_schema: {
type: "object",
properties: {
isValid: { type: "boolean" },
isRegistered: { type: "boolean" },
businessName: { type: "string" },
registrationState: { type: "string" },
registrationStatus: { type: "string" },
entityType: { type: "string" },
registrationDate: { type: "string" },
issues: { type: "array", items: { type: "string" } },
notes: { type: "string" }
}
}
});
setDocValidation(prev => ({
...prev,
sos: {
...validation,
autoVerified: true,
isValid: validation.isRegistered && (validation.registrationStatus === 'Active' || validation.registrationStatus === 'Good Standing')
}
}));
toast({
title: validation.isRegistered ? "✅ Registration Verified" : "⚠️ Verification Results",
description: validation.notes || "Check results below",
});
} catch (error) {
toast({
title: "Verification Failed",
description: "Unable to auto-verify. Please upload certificate manually.",
variant: "destructive"
});
} finally {
setValidatingDoc(null);
}
}}
disabled={validatingDoc === 'sos'}
className="bg-blue-600 hover:bg-blue-700 text-white whitespace-nowrap"
>
{validatingDoc === 'sos' ? (
<>
Checking...
>
) : (
<>
Auto-Check
>
)}
{/* Show auto-verification results */}
{docValidation.sos?.autoVerified && (
{docValidation.sos.isRegistered ? (
) : (
)}
{docValidation.sos.isRegistered ? "✅ Automatically Verified" : "ℹ Verification Results"}
{docValidation.sos.businessName && (
Business Name:
{docValidation.sos.businessName}
)}
{docValidation.sos.registrationState && (
State:
{docValidation.sos.registrationState}
)}
{docValidation.sos.registrationStatus && (
Status:
{docValidation.sos.registrationStatus}
)}
{docValidation.sos.entityType && (
EntityType:
{docValidation.sos.entityType}
)}
{docValidation.sos.registrationDate && (
Registered:
{docValidation.sos.registrationDate}
)}
{docValidation.sos.issues && docValidation.sos.issues.length > 0 && (
Issues Found:
{docValidation.sos.issues.map((issue, idx) => (
ℹ {issue}
))}
)}
{docValidation.sos.notes && (
{docValidation.sos.notes}
)}
)}
{/* Manual Upload Option with Drag & Drop */}
Or upload certificate manually for verification:
handleFileUpload(file, 'sos')}
accept=".pdf,.jpg,.jpeg,.png"
uploading={uploadingDoc === 'sos' && !docValidation.sos?.autoVerified}
uploaded={!!formData.sos_url && !docValidation.sos?.autoVerified}
uploadedFileName={formData.sos_file?.name}
disabled={uploadingDoc === 'sos' || validatingDoc === 'sos'}
/>
{/* Display manual validation results if not auto-verified, or if auto-verified failed/was skipped and manual was used */}
{docValidation.sos && !docValidation.sos.autoVerified && (
Document Validation Results:
{docValidation.sos.businessName && (
• Business: {docValidation.sos.businessName}
)}
{docValidation.sos.registrationState && (
• State: {docValidation.sos.registrationState}
)}
{docValidation.sos.expiryDate && (
• Current through: {docValidation.sos.expiryDate}
)}
{docValidation.sos.issues && docValidation.sos.issues.length > 0 && (
{docValidation.sos.issues.map((issue, idx) => (
ℹ {issue}
))}
)}
{docValidation.sos.notes && (
{docValidation.sos.notes}
)}
)}
REQUIRED: This certificate verifies your business is actively registered with your state.
You must either use Auto-Check or upload a valid certificate to proceed.
{/* NEW: Compliance Attestations Section */}
Compliance Attestations
Verify your compliance with employment and safety regulations
{/* Background Check Attestation */}
setFormData(prev => ({ ...prev, background_check_attestation: checked }))}
className="mt-1 data-[state=checked]:bg-[#0A39DF] data-[state=checked]:border-[#0A39DF]"
/>
Background Check Compliance *
I attest that my company conducts background checks on all employees in accordance with federal, state, and local laws.
We maintain proper documentation and follow all FCRA (Fair Credit Reporting Act) requirements.
{/* I-9 Compliance Attestation */}
setFormData(prev => ({ ...prev, i9_compliance_attestation: checked }))}
className="mt-1 data-[state=checked]:bg-[#0A39DF] data-[state=checked]:border-[#0A39DF]"
/>
I-9 Employment Eligibility Verification *
I attest that my company maintains complete and valid I-9 forms for all employees as required by U.S. immigration law.
All employees are verified for employment eligibility and documentation is kept on file for the required retention period.
{/* Legal Compliance Attestation */}
setFormData(prev => ({ ...prev, legal_compliance_attestation: checked }))}
className="mt-1 data-[state=checked]:bg-[#0A39DF] data-[state=checked]:border-[#0A39DF]"
/>
General Legal and Regulatory Compliance *
I attest that my company complies with all applicable federal, state, and local employment laws including but not limited to:
wage and hour laws, workers' compensation, unemployment insurance, anti-discrimination laws, OSHA safety standards,
and all licensing requirements for our industry and geographic locations.
{/* Warning Message */}
{(!formData.background_check_attestation || !formData.i9_compliance_attestation || !formData.legal_compliance_attestation) && (
Required Attestations
All compliance attestations are mandatory. By checking these boxes, you affirm that your company meets all legal requirements
and understands that providing false information may result in immediate termination of vendor partnership and potential legal action.
)}
{/* Success Message */}
{formData.background_check_attestation && formData.i9_compliance_attestation && formData.legal_compliance_attestation && (
✓ All compliance attestations completed. Your commitment to legal compliance is appreciated.
)}
)}
{/* STEP 5: Service Coverage (was Step 4) */}
{currentStep === 5 && (
Service Coverage
Select all cities and regions where you can provide services
{Object.values(formData.selected_cities).reduce((sum, cities) => sum + cities.length, 0)} Cities Selected
{/* Search and Quick Actions */}
{
// Filtering logic would be implemented here, e.g., storing search term in state
// and then filtering `Object.entries(usLocationData)` below.
// For now, it's a visual placeholder as per original outline intent.
}}
/>
{
// Select all major metro areas
const majorMetros = {
"California": ["Los Angeles", "San Francisco", "San Diego", "San Jose"],
"New York": ["New York City"],
"Texas": ["Houston", "Dallas", "Austin", "San Antonio"],
"Illinois": ["Chicago"],
"Florida": ["Miami", "Orlando", "Tampa"]
};
setFormData(prev => ({ ...prev, selected_cities: majorMetros }));
}}
>
Select Major Metros
{/* Selected Regions Summary */}
{Object.values(formData.selected_cities).reduce((sum, cities) => sum + cities.length, 0) > 0 && (
Selected Service Areas
setFormData(prev => ({ ...prev, selected_cities: {} }))}
className="text-red-600 hover:text-red-700 hover:bg-red-50"
>
Clear All
{Object.entries(formData.selected_cities).map(([state, cities]) => {
if (cities.length === 0) return null; // Don't show states with no selected cities
return (
{state}
{cities.map(city => (
{
setFormData(prev => {
const updatedStateCities = prev.selected_cities[state].filter(c => c !== city);
if (updatedStateCities.length === 0) {
const newStateCities = { ...prev.selected_cities };
delete newStateCities[state];
return { ...prev, selected_cities: newStateCities };
}
return {
...prev,
selected_cities: {
...prev.selected_cities,
[state]: updatedStateCities
}
};
});
}}
className="px-2 py-1 rounded-full text-xs font-medium bg-white border border-slate-300 text-slate-700 flex items-center gap-1 hover:bg-slate-50 transition-colors"
>
{city}
))}
);
})}
)}
{/* State and City Selection */}
{Object.entries(usLocationData).map(([state, cities]) => (
{state}
{
const isAllSelected = formData.selected_cities[state]?.length === cities.length;
setFormData(prev => {
const newStateCities = { ...prev.selected_cities };
if (isAllSelected) {
delete newStateCities[state]; // If all are selected, deselect all and remove state entry
} else {
newStateCities[state] = cities; // Select all cities for this state
}
return { ...prev, selected_cities: newStateCities };
});
}}
className="text-xs"
>
{formData.selected_cities[state]?.length === cities.length ? "Deselect All" : "Select All"}
{cities.map(city => {
const isSelected = formData.selected_cities[state]?.includes(city);
return (
{
setFormData(prev => {
const stateCities = prev.selected_cities[state] || [];
const newCities = isSelected
? stateCities.filter(c => c !== city)
: [...stateCities, city];
const newStateCities = { ...prev.selected_cities };
if (newCities.length === 0) {
delete newStateCities[state];
} else {
newStateCities[state] = newCities;
}
return {
...prev,
selected_cities: newStateCities
};
});
}}
className={`px-3 py-1.5 rounded-lg text-sm font-medium transition-all ${
isSelected
? 'bg-[#0A39DF] text-white shadow-md'
: 'bg-slate-100 text-slate-700 hover:bg-slate-200'
}`}
>
{isSelected && }
{city}
);
})}
))}
{Object.values(formData.selected_cities).reduce((sum, cities) => sum + cities.length, 0) === 0 && (
No cities selected yet
Select the cities where you can provide staffing services
)}
)}
{/* STEP 6: Rate Proposals (was Step 5) */}
{currentStep === 6 && (
Service Matrix Builder
Tell us where you shine - AI-powered rate analysis
Vendor Admin Fee (VA)
{invite?.vendor_admin_fee || 12}%
Set by Procurement
{
// Use service_address for minimum wage calculation
const localMinWage = getMinimumWageFromAddress(formData.service_address);
const newRole = {
role_name: "Custom Role",
category: "Event Staff",
employee_wage: localMinWage || 18,
markup_percentage: 20,
vendor_fee_percentage: invite?.vendor_admin_fee || 12,
client_rate: 0,
is_active: true,
ai_analysis: null,
is_custom: true,
location_rates: {}
};
const vendorFee = invite?.vendor_admin_fee || 12;
newRole.client_rate = parseFloat((newRole.employee_wage * (1 + newRole.markup_percentage / 100) * (1 + vendorFee / 100)).toFixed(2));
setFormData(prev => ({
...prev,
rate_proposals: [...prev.rate_proposals, newRole]
}));
toast({
title: "Custom Role Added",
description: "New custom role added to rate proposals"
});
}}
className="bg-[#0A39DF] hover:bg-[#0A39DF]/90"
>
Add Custom Role
🧠 AI runs in background:
Benchmarks each entry against KROW's dynamic rate matrix, historic client spend,
and market average for your region and role.
{/* Minimum Wage Detected */}
{formData.service_address && ( // Changed to service_address
Local Minimum Wage Detected: ${getMinimumWageFromAddress(formData.service_address)}/hr
Based on your business location. All payrates are pre-populated at or above this minimum. You can adjust them as needed.
Auto-Detected
)}
{/* Global Controls */}
Quick Actions:
{/* Select All / Deselect All */}
{
const allActive = formData.rate_proposals.every(r => r.is_active);
const newRates = formData.rate_proposals.map(r => ({
...r,
is_active: !allActive
}));
setFormData(prev => ({ ...prev, rate_proposals: newRates }));
}}
className="border-[#0A39DF] text-[#0A39DF] hover:bg-blue-50"
>
{formData.rate_proposals.every(r => r.is_active) ? (
<>
Deselect All
>
) : (
<>
Select All
>
)}
{/* Apply Markup to All */}
Markup:
%
{
const globalMarkupInput = document.getElementById('global_markup');
const globalMarkup = parseFloat(globalMarkupInput.value);
if (isNaN(globalMarkup) || globalMarkup <= 0) {
toast({
title: "Invalid Markup",
description: "Please enter a valid markup percentage",
variant: "destructive"
});
return;
}
const vendorFee = invite?.vendor_admin_fee || 12;
const newRates = formData.rate_proposals.map(rate => ({
...rate,
markup_percentage: globalMarkup,
client_rate: parseFloat((rate.employee_wage * (1 + globalMarkup / 100) * (1 + vendorFee / 100)).toFixed(2)),
location_rates: Object.fromEntries(
Object.entries(rate.location_rates || {}).map(([loc, lr]) => [
loc,
{
...lr,
markup_percentage: globalMarkup,
client_rate: parseFloat((lr.employee_wage * (1 + globalMarkup / 100) * (1 + vendorFee / 100)).toFixed(2))
}
])
)
}));
setFormData(prev => ({ ...prev, rate_proposals: newRates }));
toast({
title: "Markup Applied",
description: `${newRates.length} roles updated with ${globalMarkup}% markup`,
});
}}
className="bg-green-600 hover:bg-green-700 text-white"
>
Apply
{/* Enhanced Rate Cards with Location Support */}
{(() => {
// Get minimum wage based on business location (service_address)
const localMinWage = getMinimumWageFromAddress(formData.service_address);
// Pre-defined roles with wages based on local minimum wage
const defaultRoles = [
{ category: "Bartending", role: "Bartender", wageMultiplier: 1.2 },
{ category: "Kitchen and Culinary", role: "Cook", wageMultiplier: 1.2 },
{ category: "Kitchen and Culinary", role: "Line Cook", wageMultiplier: 1.2 },
{ category: "Kitchen and Culinary", role: "Sous Chef", wageMultiplier: 1.6 },
{ category: "Kitchen and Culinary", role: "Executive Chef", wageMultiplier: 2.3 },
{ category: "Kitchen and Culinary", role: "Prep Cook", wageMultiplier: 1.0 },
{ category: "Concessions", role: "Concessions Attendant", wageMultiplier: 1.0 },
{ category: "Concessions", role: "Cashier", wageMultiplier: 1.0 },
{ category: "Facilities", role: "Janitor", wageMultiplier: 1.05 },
{ category: "Facilities", role: "Maintenance Technician", wageMultiplier: 1.4 },
{ category: "Bartending", role: "Bar Back", wageMultiplier: 1.0 },
{ category: "Security", role: "Security Guard", wageMultiplier: 1.3 },
{ category: "Event Staff", role: "Event Server", wageMultiplier: 1.05 },
{ category: "Event Staff", role: "Event Coordinator", wageMultiplier: 1.6 },
{ category: "Management", role: "General Manager", wageMultiplier: 2.6 },
{ category: "Management", role: "Assistant Manager", wageMultiplier: 1.8 },
];
// Initialize rate proposals if empty
if (formData.rate_proposals.length === 0) {
const initialRates = defaultRoles.map(r => {
const calculatedWage = parseFloat((localMinWage * r.wageMultiplier).toFixed(2));
const vendorFee = invite?.vendor_admin_fee || 12;
return {
category: r.category,
role_name: r.role,
employee_wage: calculatedWage,
markup_percentage: 20,
vendor_fee_percentage: vendorFee,
client_rate: parseFloat((calculatedWage * 1.2 * (1 + (vendorFee / 100))).toFixed(2)),
is_active: true,
ai_analysis: null,
is_custom: false,
location_rates: {} // Initialize empty object for location-specific rates
};
});
// Using a functional update to ensure we get the latest state
setFormData(prev => ({ ...prev, rate_proposals: initialRates }));
}
return null; // This IIFE does not render anything directly
})()}
{formData.rate_proposals.map((rate, idx) => {
const vendorFee = invite?.vendor_admin_fee || 12;
const selectedLocations = Object.entries(formData.selected_cities).flatMap(([state, cities]) =>
cities.map(city => `${city}, ${state}`)
);
// Determine status based on client rate vs market average (simulated)
const marketAverage = rate.employee_wage * 1.35;
let status = "competitive";
let statusIcon = "✓";
let statusText = "Good Deal";
let statusClass = "bg-green-100 text-green-700";
if (rate.client_rate > marketAverage * 1.15) {
status = "risk";
statusIcon = "!";
statusText = "Risk Zone";
statusClass = "bg-red-100 text-red-700";
} else if (rate.client_rate > marketAverage * 1.05) {
status = "market";
statusIcon = "~";
statusText = "At Market";
statusClass = "bg-blue-100 text-blue-700";
}
return (
{/* Toggle */}
{
const newRates = [...formData.rate_proposals];
newRates[idx].is_active = !newRates[idx].is_active;
setFormData(prev => ({ ...prev, rate_proposals: newRates }));
}}
className={`w-12 h-6 rounded-full transition-colors relative ${rate.is_active ? 'bg-green-500' : 'bg-slate-300'}`}
>
{/* Role Info */}
{/* Position */}
Position
{rate.is_custom ? (
{
const newRates = [...formData.rate_proposals];
newRates[idx].role_name = e.target.value;
setFormData(prev => ({ ...prev, rate_proposals: newRates }));
}}
className="h-8 text-sm"
disabled={!rate.is_active}
/>
) : (
{rate.role_name}
)}
{rate.category}
{/* Payrate */}
{/* Markup */}
{/* Vendor Fee % (Read-only) */}
Vendor Fee %
{vendorFee}%
{/* Proposed Bill Rate */}
Proposed Bill Rate
${rate.client_rate}
{/* Status */}
Status
{statusIcon} {statusText}
{/* Actions */}
{rate.is_custom && (
{
setFormData(prev => ({
...prev,
rate_proposals: prev.rate_proposals.filter((_, i) => i !== idx)
}));
}}
className="text-red-600 hover:text-red-700 hover:bg-red-50"
>
)}
{/* NEW: Location-Specific Rates Section */}
{rate.is_active && selectedLocations.length > 0 && ( // Show if active and at least one location is selected
Location-Specific Rates
{selectedLocations.length} Locations Available
{selectedLocations.map(location => {
// Default to the base rate if no specific location rate is set
const locationRate = rate.location_rates?.[location] || {
employee_wage: rate.employee_wage,
markup_percentage: rate.markup_percentage,
client_rate: rate.client_rate
};
return (
);
})}
{
// Copy base rate to all locations
setFormData(prev => {
const newRates = [...prev.rate_proposals];
newRates[idx].location_rates = {};
selectedLocations.forEach(location => {
newRates[idx].location_rates[location] = {
employee_wage: rate.employee_wage,
markup_percentage: rate.markup_percentage,
client_rate: rate.client_rate
};
});
return { ...prev, rate_proposals: newRates };
});
toast({
title: "Rates Copied",
description: "Base rate applied to all locations"
});
}}
>
Apply Base Rate to All Locations
)}
{/* Expanded Details - Visual Breakdown */}
{rate.is_active && (
{/* Cost Breakdown Bar */}
Cost Breakdown
${rate.employee_wage}
{rate.markup_percentage}%
{vendorFee}%
Payrate: ${rate.employee_wage}/hr
Markup: {rate.markup_percentage}% (+${(rate.employee_wage * rate.markup_percentage / 100).toFixed(2)})
VA: {vendorFee}% (+${(rate.employee_wage * (1 + rate.markup_percentage / 100) * vendorFee / 100).toFixed(2)})
{/* AI Insights */}
💡 AI Insights
Market Avg:
${marketAverage.toFixed(2)}/hr
Your Rate:
${rate.client_rate}/hr
Difference:
{rate.client_rate < marketAverage ? '-' : '+'}${Math.abs(rate.client_rate - marketAverage).toFixed(2)}
Fill Rate:
{status === 'competitive' ? '95%' : status === 'market' ? '75%' : '45%'}/yr
{/* Status Message */}
{status === 'competitive' && (
✓ Good Deal: Your rate is {((1 - rate.client_rate / marketAverage) * 100).toFixed(0)}% below market average.
Highly competitive with predicted 95% fill rate.
)}
{status === 'market' && (
~ At Market: Your rate aligns with market average.
Competitive with predicted 75% fill rate.
)}
{status === 'risk' && (
! Risk Zone: Your rate is {(((rate.client_rate / marketAverage) - 1) * 100).toFixed(0)}% above market average.
This may reduce competitiveness with predicted 45% fill rate. Consider lowering markup.
)}
)}
);
})}
{/* Summary Stats */}
{formData.rate_proposals.filter(r => r.is_active && r.client_rate < (r.employee_wage * 1.35 * 1.05)).length}
✓ Good Deals
{formData.rate_proposals.filter(r => r.is_active && r.client_rate >= (r.employee_wage * 1.35 * 1.05) && r.client_rate <= (r.employee_wage * 1.35 * 1.15)).length}
~ At Market
{formData.rate_proposals.filter(r => r.is_active && r.client_rate > (r.employee_wage * 1.35 * 1.15)).length}
! Risk Zone
{formData.rate_proposals.filter(r => r.is_active).length}
Active Roles
)}
{/* STEP 7: AI Intelligence (was Step 6) */}
{currentStep === 7 && (
AI Intelligence
Market insights & recommendations
{!aiInsights && (
Generate Insights
)}
{!aiInsights ? (
Ready to analyze your application
Get AI-powered insights on your competitive positioning
) : (
{/* Overall Score */}
Overall Assessment
{aiInsights.overall_score}/100
{aiInsights.competitive_position}
{/* Strengths */}
{aiInsights.strengths && aiInsights.strengths.length > 0 && (
Strengths
{aiInsights.strengths.map((strength, idx) => (
))}
)}
{/* Recommendations */}
{aiInsights.recommendations && aiInsights.recommendations.length > 0 && (
Recommendations
{aiInsights.recommendations.map((rec, idx) => (
))}
)}
{/* Analysis Details */}
Market Alignment
{aiInsights.market_alignment}
Pricing Analysis
{aiInsights.pricing_analysis}
)}
)}
{/* Navigation Buttons */}
{currentStep > 0 && (
Back
{currentStep < steps.length - 1 ? (
Next Step
) : (
{submitApplicationMutation.isPending ? (
<>
Submitting...
>
) : (
<>
Submit Application
>
)}
)}
)}
);
}