Files
Krow-workspace/frontend-web/src/pages/SmartVendorOnboarding.jsx
bwnyasse 80cd49deb5 feat(Makefile): install frontend dependencies on dev command
feat(Makefile): patch Layout.jsx queryKey for local development
feat(frontend-web): mock base44 client for local development with role switching
feat(frontend-web): add event assignment modal with conflict detection and bulk assign
feat(frontend-web): add client dashboard with key metrics and quick actions
feat(frontend-web): add layout component with role-based navigation
feat(frontend-web): update various pages to use "@/components" alias
feat(frontend-web): update create event page with ai assistant toggle
feat(frontend-web): update dashboard page with new components
feat(frontend-web): update events page with quick assign popover
feat(frontend-web): update invite vendor page with hover card
feat(frontend-web): update messages page with conversation list and message thread
feat(frontend-web): update operator dashboard page with new components
feat(frontend-web): update partner management page with new components
feat(frontend-web): update permissions page with new components
feat(frontend-web): update procurement dashboard page with new components
feat(frontend-web): update smart vendor onboarding page with new components
feat(frontend-web): update staff directory page with new components
feat(frontend-web): update teams page with new components
feat(frontend-web): update user management page with new components
feat(frontend-web): update vendor compliance page with new components
feat(frontend-web): update main.jsx to include react query provider

feat: add vendor marketplace page
feat: add global import fix to prepare-export script
feat: add patch-layout-query-key script to fix query key
feat: update patch-base44-client script to use a more robust method
2025-11-13 14:56:31 -05:00

2889 lines
143 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 (
<div className="relative">
<Label htmlFor={label}>
{label} {required && <span className="text-red-500">*</span>}
</Label>
<div className="relative mt-2">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-slate-400" />
<Input
ref={inputRef}
value={inputValue}
onChange={(e) => {
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"
/>
</div>
{showSuggestions && suggestions.length > 0 && (
<div className="absolute z-50 w-full mt-1 bg-white border border-slate-200 rounded-lg shadow-lg max-h-60 overflow-y-auto">
{suggestions.map((suggestion, idx) => (
<button
key={idx}
type="button"
onClick={() => 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"
>
<div className="flex items-start gap-2">
<MapPin className="w-4 h-4 text-slate-400 mt-0.5 flex-shrink-0" />
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-slate-900 truncate">{suggestion.address}</p>
<p className="text-xs text-slate-500">{suggestion.city}, {suggestion.state} {suggestion.zip}</p>
</div>
</div>
</button>
))}
</div>
)}
</div>
);
};
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: `
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px;">
<h2>Application Received Successfully!</h2>
<p>Dear ${formData.primary_contact_name},</p>
<p>Thank you for completing your vendor application with KROW.</p>
<div style="background: #f0f9ff; padding: 20px; border-radius: 8px; margin: 20px 0;">
<p style="margin: 0;"><strong>Application Summary:</strong></p>
<p style="margin: 8px 0;">Vendor Number: <strong>${vendorNumber}</strong></p>
<p style="margin: 8px 0;">Company: <strong>${formData.legal_name}</strong></p>
<p style="margin: 8px 0;">Status: <strong>Under Review</strong></p>
</div>
<p>Our procurement team will review your application and get back to you within 2-3 business days.</p>
<p>Best regards,<br>KROW Team</p>
</div>
`
});
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 (
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-purple-50 py-8 px-4">
<div className="max-w-5xl mx-auto">
{/* STEP 0: Welcome Screen */}
{currentStep === 0 && (
<Card className="border-2 border-[#0A39DF]/20 shadow-2xl">
<CardContent className="p-12">
<div className="text-center space-y-6">
{/* Logo - KROW LOGO */}
<div className="flex justify-center mb-6">
<div className="w-32 h-32 bg-gradient-to-r from-[#0A39DF] to-[#1C323E] rounded-full flex items-center justify-center shadow-xl p-6">
<img
src="https://qtrypzzcjebvfcihiynt.supabase.co/storage/v1/object/public/base44-prod/public/68fc6cf01386035c266e7a5d/3ba390829_KROWlogo.png"
alt="KROW Logo"
className="w-full h-full object-contain"
/>
</div>
</div>
{/* Welcome Message */}
<div>
<h1 className="text-4xl font-bold bg-gradient-to-r from-[#0A39DF] to-[#1C323E] bg-clip-text text-transparent mb-4">
Welcome to KROW
</h1>
<p className="text-xl text-slate-600 leading-relaxed max-w-2xl mx-auto">
The Workforce Control Tower that connects you to the most trusted procurement and operator network.
</p>
<p className="text-lg text-slate-500 mt-4">
Let's begin your onboarding journey, it'll take just a few minutes to get your company fully connected.
</p>
</div>
{/* Progress indicator */}
<div className="pt-8">
<div className="flex items-center justify-center gap-2 mb-4">
<div className="w-full max-w-md bg-slate-200 h-3 rounded-full overflow-hidden">
<div
className="h-full bg-gradient-to-r from-[#0A39DF] to-[#1C323E] transition-all duration-500"
style={{ width: '0%' }}
/>
</div>
<span className="text-sm font-semibold text-slate-600 whitespace-nowrap">0% Complete</span>
</div>
<p className="text-sm text-slate-500">Step 1 of 8: NDA & Trust Agreement</p>
</div>
{/* Start Button */}
<Button
onClick={handleNext}
className="mt-8 bg-gradient-to-r from-[#0A39DF] to-[#1C323E] hover:from-[#0A39DF]/90 hover:to-[#1C323E]/90 text-white px-12 py-6 text-lg font-bold shadow-xl"
>
Start Onboarding
<ArrowRight className="w-5 h-5 ml-2" />
</Button>
{invite && (
<Badge className="mt-6 bg-green-100 text-green-700 px-4 py-2">
Invited by {invite.invited_by}
</Badge>
)}
</div>
</CardContent>
</Card>
)}
{/* Steps 1-7: Show header and progress only if not on welcome screen */}
{currentStep > 0 && (
<>
{/* Header */}
<div className="text-center mb-8">
<div className="flex items-center justify-center gap-2 mb-4">
<img
src="https://qtrypzzcjebvfcihiynt.supabase.co/storage/v1/object/public/base44-prod/public/68fc6cf01386035c266e7a5d/3ba390829_KROWlogo.png"
alt="KROW Logo"
className="w-8 h-8"
/>
<h1 className="text-4xl font-bold bg-gradient-to-r from-[#0A39DF] to-[#1C323E] bg-clip-text text-transparent">
Smart Vendor Onboarding
</h1>
</div>
<p className="text-slate-600">Real-time market analysis Predictive scoring Instant validation</p>
{invite && (
<Badge className="mt-2 bg-green-100 text-green-700">
Invited by {invite.invited_by}
</Badge>
)}
</div>
{/* Progress Bar */}
<Card className="mb-6 border-2 border-[#0A39DF]/20">
<CardContent className="p-6">
<div className="flex items-center justify-between mb-4">
<p className="text-sm font-semibold text-slate-700">Step {currentStep} of {steps.length - 1}</p>
<p className="text-sm font-semibold text-[#0A39DF]">{Math.round(progressPercentage)}% Complete</p>
</div>
<Progress value={progressPercentage} className="h-3 mb-6" />
{/* Step indicators */}
<div className="grid grid-cols-7 gap-2">
{steps.slice(1).map((step) => {
const Icon = step.icon;
const isActive = currentStep === step.number;
const isComplete = currentStep > step.number;
return (
<button
key={step.number}
onClick={() => 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'
}`}
>
<Icon className="w-5 h-5 mx-auto mb-1" />
<p className="text-xs font-medium truncate">{step.title}</p>
</button>
);
})}
</div>
</CardContent>
</Card>
</>
)}
{/* STEP 1: NDA & Trust Agreement */}
{currentStep === 1 && (
<Card className="border-2 border-slate-200">
<CardHeader className="bg-gradient-to-r from-purple-50 to-indigo-50 border-b">
<div className="flex items-center gap-3">
<div className="w-12 h-12 bg-gradient-to-r from-purple-600 to-indigo-600 rounded-xl flex items-center justify-center">
<Shield className="w-6 h-6 text-white" />
</div>
<div>
<CardTitle className="text-2xl">NDA & Trust Agreement</CardTitle>
<p className="text-sm text-slate-600 mt-1">Sign and accept - Date and time will be recorded</p>
</div>
</div>
</CardHeader>
<CardContent className="p-6 space-y-6">
{/* NDA Document Viewer */}
<DocumentViewer
documentUrl="https://qtrypzzcjebvfcihiynt.supabase.co/storage/v1/object/public/base44-prod/public/68fc6cf01386035c266e7a5d/187424574_LegendaryEventStaffingFOODBUYVendorNDA.pdf"
documentName="Confidentiality & Non-Disclosure Agreement"
documentType="NDA"
onAcknowledge={handleSignNDA}
initialNotes=""
isAcknowledged={formData.nda_acknowledged}
timeSpent={0}
/>
{/* Warning if not signed */}
{!formData.nda_acknowledged && (
<div className="p-4 bg-purple-50 border border-purple-200 rounded-lg flex items-start gap-2">
<AlertCircle className="w-5 h-5 text-purple-600 mt-0.5 flex-shrink-0" />
<p className="text-sm text-purple-800">
You must review and sign the NDA to proceed with onboarding
</p>
</div>
)}
{/* Success message if signed - NOW WITH SIGNATURE IMAGE */}
{formData.nda_acknowledged && (
<div className="p-6 bg-green-50 border-2 border-green-300 rounded-xl">
<div className="text-center">
<div className="w-16 h-16 bg-green-500 rounded-full flex items-center justify-center mx-auto mb-4">
<Check className="w-10 h-10 text-white" />
</div>
<h4 className="text-xl font-bold text-green-900 mb-3"> NDA Signed Successfully</h4>
{/* Signature and Details Card */}
<div className="bg-white p-6 rounded-lg border-2 border-green-300 max-w-md mx-auto">
{/* Signer Name */}
<div className="mb-4">
<p className="text-xs text-slate-500 uppercase tracking-wide mb-1">Signed by:</p>
<p className="text-lg font-bold text-slate-900">{formData.nda_signed_by}</p>
</div>
{/* Signature Image */}
{formData.nda_signature_image && (
<div className="mb-4 p-4 bg-slate-50 rounded-lg border-2 border-slate-300">
<p className="text-xs text-slate-500 uppercase tracking-wide mb-2">Digital Signature:</p>
<img
src={formData.nda_signature_image}
alt="Signature"
className="w-full h-24 object-contain bg-white rounded border border-slate-300"
/>
</div>
)}
{/* Date and Time */}
<div className="space-y-2 text-left text-sm border-t border-slate-200 pt-4">
<div className="flex justify-between">
<span className="font-semibold text-slate-700">Date:</span>
<span className="text-slate-900">{formData.nda_signature_date}</span>
</div>
<div className="flex justify-between">
<span className="font-semibold text-slate-700">Time:</span>
<span className="text-slate-900">{formData.nda_signature_time}</span>
</div>
</div>
</div>
<p className="text-green-700 mt-4 font-semibold">
You may now proceed to the next step
</p>
</div>
</div>
)}
</CardContent>
</Card>
)}
{/* STEP 2: Foodbuy Contract Review (was Step 1) */}
{currentStep === 2 && (
<Card className="border-2 border-slate-200">
<CardHeader className="bg-gradient-to-r from-blue-50 to-indigo-50 border-b">
<div className="flex items-center gap-3">
<div className="w-12 h-12 bg-gradient-to-r from-blue-600 to-indigo-600 rounded-xl flex items-center justify-center">
<FileText className="w-6 h-6 text-white" />
</div>
<div>
<CardTitle className="text-2xl">Foodbuy Contract Review</CardTitle>
<p className="text-sm text-slate-600 mt-1">Please review the terms before proceeding</p>
</div>
</div>
</CardHeader>
<CardContent className="p-6 space-y-6">
{/* Contract Document Viewer */}
<DocumentViewer
documentUrl="https://qtrypzzcjebvfcihiynt.supabase.co/storage/v1/object/public/base44-prod/public/68fc6cf01386035c266e7a5d/2c22905a2_FoodbuyDraftContract.pdf"
documentName="Foodbuy Temporary Staffing Agreement"
documentType="Contract"
onAcknowledge={(acknowledgmentData) => {
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 */}
<div className="mt-6 space-y-4">
<div className="bg-gradient-to-br from-blue-50 to-indigo-50 p-6 rounded-xl border-2 border-blue-200">
<h3 className="text-lg font-bold text-[#1C323E] mb-4 flex items-center gap-2">
<Target className="w-6 h-6 text-blue-600" />
Performance Requirements & Service Standards
</h3>
{/* Fill Rate & KPIs */}
<div className="bg-white rounded-lg p-5 mb-4 shadow-sm border border-blue-100">
<h4 className="font-semibold text-[#1C323E] mb-3 flex items-center gap-2">
<TrendingUp className="w-5 h-5 text-green-600" />
Key Performance Indicators (KPIs)
</h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="flex justify-between items-center p-3 bg-slate-50 rounded-lg">
<span className="text-sm font-medium text-slate-700">Fill Rate Target</span>
<Badge className="bg-green-100 text-green-700 font-bold"> 95%</Badge>
</div>
<div className="flex justify-between items-center p-3 bg-slate-50 rounded-lg">
<span className="text-sm font-medium text-slate-700">On-Time Arrival</span>
<Badge className="bg-green-100 text-green-700 font-bold"> 98%</Badge>
</div>
<div className="flex justify-between items-center p-3 bg-slate-50 rounded-lg">
<span className="text-sm font-medium text-slate-700">Client Satisfaction (CSAT)</span>
<Badge className="bg-green-100 text-green-700 font-bold"> 4.5/5.0</Badge>
</div>
<div className="flex justify-between items-center p-3 bg-slate-50 rounded-lg">
<span className="text-sm font-medium text-slate-700">Cancellation Rate</span>
<Badge className="bg-amber-100 text-amber-700 font-bold"> 3%</Badge>
</div>
<div className="flex justify-between items-center p-3 bg-slate-50 rounded-lg">
<span className="text-sm font-medium text-slate-700">No-Show Rate</span>
<Badge className="bg-amber-100 text-amber-700 font-bold"> 1%</Badge>
</div>
<div className="flex justify-between items-center p-3 bg-slate-50 rounded-lg">
<span className="text-sm font-medium text-slate-700">Response Time</span>
<Badge className="bg-blue-100 text-blue-700 font-bold"> 2 hours</Badge>
</div>
</div>
</div>
{/* Compliance & Attestations */}
<div className="bg-white rounded-lg p-5 mb-4 shadow-sm border border-blue-100">
<h4 className="font-semibold text-[#1C323E] mb-3 flex items-center gap-2">
<Shield className="w-5 h-5 text-purple-600" />
Compliance & Attestation Requirements
</h4>
<div className="space-y-3">
<div className="flex items-start gap-3 p-3 bg-green-50 rounded-lg border border-green-200">
<CheckCircle2 className="w-5 h-5 text-green-600 mt-0.5 flex-shrink-0" />
<div>
<p className="text-sm font-semibold text-green-900">Background Check Compliance</p>
<p className="text-xs text-green-700 mt-1">All employees must have valid background checks per federal and state requirements</p>
</div>
</div>
<div className="flex items-start gap-3 p-3 bg-green-50 rounded-lg border border-green-200">
<CheckCircle2 className="w-5 h-5 text-green-600 mt-0.5 flex-shrink-0" />
<div>
<p className="text-sm font-semibold text-green-900">I-9 Employment Eligibility</p>
<p className="text-xs text-green-700 mt-1">Maintain complete and valid I-9 forms for all employees with proper documentation</p>
</div>
</div>
<div className="flex items-start gap-3 p-3 bg-green-50 rounded-lg border border-green-200">
<CheckCircle2 className="w-5 h-5 text-green-600 mt-0.5 flex-shrink-0" />
<div>
<p className="text-sm font-semibold text-green-900">Insurance & Liability Coverage</p>
<p className="text-xs text-green-700 mt-1">General liability insurance minimum $1M, Workers' Compensation as required by law</p>
</div>
</div>
<div className="flex items-start gap-3 p-3 bg-green-50 rounded-lg border border-green-200">
<CheckCircle2 className="w-5 h-5 text-green-600 mt-0.5 flex-shrink-0" />
<div>
<p className="text-sm font-semibold text-green-900">Legal & Regulatory Compliance</p>
<p className="text-xs text-green-700 mt-1">Comply with all federal, state, and local employment laws including wage & hour, safety, and anti-discrimination</p>
</div>
</div>
<div className="flex items-start gap-3 p-3 bg-green-50 rounded-lg border border-green-200">
<CheckCircle2 className="w-5 h-5 text-green-600 mt-0.5 flex-shrink-0" />
<div>
<p className="text-sm font-semibold text-green-900">Tax & Payroll Compliance</p>
<p className="text-xs text-green-700 mt-1">Proper W-9 on file, accurate tax withholding, and timely payment of all employment taxes</p>
</div>
</div>
</div>
</div>
{/* VA Fee Structure */}
<div className="bg-white rounded-lg p-5 shadow-sm border border-blue-100">
<h4 className="font-semibold text-[#1C323E] mb-3 flex items-center gap-2">
<DollarSign className="w-5 h-5 text-purple-600" />
Vendor Fee Structure
</h4>
<div className="space-y-3">
<div className="p-4 bg-purple-50 rounded-lg border-2 border-purple-200">
<div className="flex items-center justify-between mb-2">
<span className="text-sm font-medium text-purple-900">Vendor Fee</span>
<Badge className="bg-purple-600 text-white text-lg font-bold px-4 py-1">
{invite?.vendor_admin_fee || 12}%
</Badge>
</div>
<p className="text-xs text-purple-700">
Cost of doing business with Foodbuy as your partner
</p>
</div>
<div className="p-4 bg-slate-50 rounded-lg border border-slate-200">
<h5 className="text-sm font-semibold text-[#1C323E] mb-3">Fee Calculation Example</h5>
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-slate-600">Employee Wage</span>
<span className="font-semibold">$18.50/hr</span>
</div>
<div className="flex justify-between">
<span className="text-slate-600">Your Markup (20%)</span>
<span className="font-semibold text-blue-600">+$3.70/hr</span>
</div>
<div className="flex justify-between pt-2 border-t border-slate-300">
<span className="text-slate-700 font-medium">Bill Rate (to client)</span>
<span className="font-semibold">$22.20/hr</span>
</div>
<div className="flex justify-between">
<span className="text-slate-600">Vendor Fee ({invite?.vendor_admin_fee || 12}%)</span>
<span className="font-semibold text-red-600">-${((22.20 * (invite?.vendor_admin_fee || 12)) / 100).toFixed(2)}/hr</span>
</div>
<div className="flex justify-between pt-2 border-t-2 border-slate-400">
<span className="text-[#1C323E] font-bold">You Receive</span>
<span className="font-bold text-green-600 text-lg">${(22.20 - (22.20 * (invite?.vendor_admin_fee || 12) / 100)).toFixed(2)}/hr</span>
</div>
</div>
</div>
<div className="p-3 bg-blue-50 rounded-lg border border-blue-200">
<p className="text-xs text-blue-900">
<strong>💡 What the Vendor Fee Covers:</strong> Platform access, procurement management, client relationships, compliance oversight, payment processing, insurance coordination, and KROW system support.
</p>
</div>
</div>
</div>
</div>
{/* Acknowledgment Checkbox */}
<div className="bg-slate-50 p-6 rounded-lg border-2 border-slate-200">
<div className="flex items-start gap-3">
<Checkbox
id="contract_va_fee_acknowledged"
checked={formData.contract_va_fee_acknowledged}
onCheckedChange={(checked) => setFormData(prev => ({ ...prev, contract_va_fee_acknowledged: checked }))}
className="mt-1 data-[state=checked]:bg-[#0A39DF] data-[state=checked]:border-[#0A39DF]"
/>
<div className="flex-1">
<Label htmlFor="contract_va_fee_acknowledged" className="font-semibold cursor-pointer text-base">
I understand the performance requirements and vendor fee structure <span className="text-red-500">*</span>
</Label>
<p className="text-sm text-slate-600 mt-1">
Including the {invite?.vendor_admin_fee || 12}% vendor fee and service standards outlined above
</p>
</div>
</div>
</div>
</div>
{(!formData.contract_acknowledged || !formData.contract_va_fee_acknowledged) && (
<div className="p-4 bg-blue-50 border border-blue-200 rounded-lg flex items-start gap-2">
<AlertCircle className="w-5 h-5 text-blue-600 mt-0.5 flex-shrink-0" />
<p className="text-sm text-blue-800">
You must review and acknowledge the contract terms to proceed with onboarding
</p>
</div>
)}
</CardContent>
</Card>
)}
{/* STEP 3: Business Identity (was Step 2) */}
{currentStep === 3 && (
<Card className="border-2 border-slate-200">
<CardHeader className="bg-gradient-to-r from-slate-50 to-blue-50 border-b">
<div className="flex items-center gap-3">
<div className="w-12 h-12 bg-[#0A39DF] rounded-xl flex items-center justify-center">
<Building2 className="w-6 h-6 text-white" />
</div>
<div>
<CardTitle className="text-2xl">Business Identity</CardTitle>
<p className="text-sm text-slate-600 mt-1">Let's set your stage</p>
</div>
</div>
</CardHeader>
<CardContent className="p-6 space-y-6">
{/* Legal Business Name */}
<div className="grid grid-cols-2 gap-4">
<div>
<Label htmlFor="legal_name">
Legal Business Name <span className="text-red-500">*</span>
</Label>
<Input
id="legal_name"
value={formData.legal_name}
onChange={(e) => setFormData(prev => ({ ...prev, legal_name: e.target.value }))}
placeholder="ABC Staffing Solutions LLC"
className="mt-2"
/>
</div>
<div>
<Label htmlFor="dba">Doing Business As (DBA)</Label>
<Input
id="dba"
value={formData.dba}
onChange={(e) => setFormData(prev => ({ ...prev, dba: e.target.value }))}
placeholder="ABC Staffing"
className="mt-2"
/>
</div>
</div>
{/* Tax ID & Business Type */}
<div className="grid grid-cols-2 gap-4">
<div>
<Label htmlFor="tax_id">
Federal Tax ID / EIN <span className="text-red-500">*</span>
</Label>
<Input
id="tax_id"
value={formData.tax_id}
onChange={(e) => setFormData(prev => ({ ...prev, tax_id: e.target.value }))}
placeholder="XX-XXXXXXX"
className="mt-2"
/>
</div>
<div>
<Label htmlFor="business_type">
Business Entity <span className="text-red-500">*</span>
</Label>
<Select value={formData.business_type} onValueChange={(value) => setFormData(prev => ({ ...prev, business_type: value }))}>
<SelectTrigger className="mt-2">
<SelectValue placeholder="Select entity type" />
</SelectTrigger>
<SelectContent>
<SelectItem value="LLC">LLC</SelectItem>
<SelectItem value="Corporation">Corporation</SelectItem>
<SelectItem value="Partnership">Partnership</SelectItem>
<SelectItem value="Sole Proprietorship">Sole Proprietorship</SelectItem>
</SelectContent>
</Select>
</div>
</div>
{/* Contact Information */}
<div className="grid grid-cols-3 gap-4">
<div>
<Label htmlFor="contact_name">
Primary Contact Name <span className="text-red-500">*</span>
</Label>
<Input
id="contact_name"
value={formData.primary_contact_name}
onChange={(e) => setFormData(prev => ({ ...prev, primary_contact_name: e.target.value }))}
placeholder="John Smith"
className="mt-2"
/>
</div>
<div>
<Label htmlFor="contact_email">
Contact Email <span className="text-red-500">*</span>
</Label>
<Input
id="contact_email"
type="email"
value={formData.primary_contact_email}
onChange={(e) => setFormData(prev => ({ ...prev, primary_contact_email: e.target.value }))}
placeholder="john@abcstaffing.com"
className="mt-2"
/>
</div>
<div>
<Label htmlFor="contact_phone">Contact Phone <span className="text-red-500">*</span></Label>
<Input
id="contact_phone"
value={formData.primary_contact_phone}
onChange={(e) => setFormData(prev => ({ ...prev, primary_contact_phone: e.target.value }))}
placeholder="(555) 123-4567"
className="mt-2"
/>
</div>
</div>
{/* Google Address Autocomplete - SERVICE ADDRESS FIRST */}
<div>
<GoogleAddressInput
value={formData.service_address}
onChange={(value) => setFormData(prev => ({ ...prev, service_address: value }))}
placeholder="123 Main Street, Suite 100, San Francisco, CA 94105"
label="Service Address"
required
/>
</div>
{/* Same as service checkbox */}
<div className="flex items-center gap-2">
<Checkbox
id="same_as_service"
checked={formData.same_as_billing}
onCheckedChange={(checked) => setFormData(prev => ({ ...prev, same_as_billing: checked }))}
className="data-[state=checked]:bg-[#0A39DF] data-[state=checked]:border-[#0A39DF]"
/>
<Label htmlFor="same_as_service" className="text-sm cursor-pointer">
Billing address is same as service address
</Label>
</div>
{/* Billing Address - only show if checkbox is unchecked */}
{!formData.same_as_billing && (
<div>
<GoogleAddressInput
value={formData.billing_address}
onChange={(value) => setFormData(prev => ({ ...prev, billing_address: value }))}
placeholder="456 Office Blvd, Oakland, CA 94612"
label="Billing Address"
/>
</div>
)}
{/* NEW FIELDS: Staff Count & Software */}
<div className="border-t border-slate-200 pt-6 mt-6">
<h3 className="font-semibold text-lg mb-4 flex items-center gap-2">
<Users className="w-5 h-5 text-[#0A39DF]" />
Operations Information
</h3>
<div className="grid grid-cols-2 gap-4">
{/* Total Employees */}
<div>
<Label htmlFor="total_employees">
How many staff members do you have? <span className="text-red-500">*</span>
</Label>
<Input
id="total_employees"
type="number"
value={formData.total_employees}
onChange={(e) => setFormData(prev => ({ ...prev, total_employees: e.target.value }))}
placeholder="e.g., 250"
className="mt-2"
/>
<p className="text-xs text-slate-500 mt-1">Total active workforce members</p>
</div>
{/* Has Software */}
<div>
<Label htmlFor="has_software">
Do you use operational software? <span className="text-red-500">*</span>
</Label>
<Select value={formData.has_software} onValueChange={(value) => {
setFormData(prev => ({
...prev,
has_software: value,
software_name: value === "no" ? "traditional" : ""
}));
}}>
<SelectTrigger className="mt-2">
<SelectValue placeholder="Select an option" />
</SelectTrigger>
<SelectContent>
<SelectItem value="yes">Yes, we have software</SelectItem>
<SelectItem value="no">No, traditional operations</SelectItem>
</SelectContent>
</Select>
</div>
</div>
{/* Software Details */}
{formData.has_software === "yes" && (
<div className="grid grid-cols-2 gap-4 mt-4">
<div>
<Label htmlFor="software_type">
Software Type <span className="text-red-500">*</span>
</Label>
<Select value={formData.software_type} onValueChange={(value) => setFormData(prev => ({ ...prev, software_type: value }))}>
<SelectTrigger className="mt-2">
<SelectValue placeholder="Select type" />
</SelectTrigger>
<SelectContent>
<SelectItem value="platform">Third-party Platform</SelectItem>
<SelectItem value="internal">Internal/Custom System</SelectItem>
<SelectItem value="building">Building on KROW</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label htmlFor="software_name">
Software/Platform Name <span className="text-red-500">*</span>
</Label>
<Input
id="software_name"
value={formData.software_name}
onChange={(e) => setFormData(prev => ({ ...prev, software_name: e.target.value }))}
placeholder={formData.software_type === "internal" ? "Internal System" : "e.g., Instawork, Qwick"}
className="mt-2"
/>
</div>
</div>
)}
</div>
</CardContent>
</Card>
)}
{/* STEP 4: Documents & Validation (was Step 3) */}
{currentStep === 4 && (
<Card className="border-2 border-slate-200">
<CardHeader className="bg-gradient-to-r from-slate-50 to-blue-50 border-b">
<div className="flex items-center gap-3">
<div className="w-12 h-12 bg-[#0A39DF] rounded-xl flex items-center justify-center">
<Shield className="w-6 h-6 text-white" />
</div>
<div>
<CardTitle className="text-2xl">Documents & Validation</CardTitle>
<p className="text-sm text-slate-600 mt-1">Compliance essentials - AI powered verification</p>
</div>
</div>
</CardHeader>
<CardContent className="p-6 space-y-6">
{/* W-9 Upload */}
<div>
<div className="flex items-start justify-between mb-4">
<div>
<h3 className="font-semibold flex items-center gap-2">
<FileText className="w-5 h-5 text-[#0A39DF]" />
W-9 Tax Form <span className="text-red-500">*</span>
</h3>
<p className="text-sm text-slate-600 mt-1">Federal tax classification document</p>
</div>
{docValidation.w9 && (
<Badge className={docValidation.w9.isValid ? "bg-green-100 text-green-700" : "bg-blue-100 text-blue-700"}>
{docValidation.w9.isValid ? "✅ Validated" : " Review"}
</Badge>
)}
</div>
<DragDropFileUpload
onFileSelect={(file) => 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 && (
<div className="mt-3 p-3 bg-slate-50 rounded-lg">
<p className="text-sm font-medium text-slate-700 mb-1">Validation Results:</p>
{docValidation.w9.businessName && (
<p className="text-xs text-slate-600"> Business: {docValidation.w9.businessName}</p>
)}
{docValidation.w9.issues && docValidation.w9.issues.length > 0 && (
<div className="mt-2">
{docValidation.w9.issues.map((issue, idx) => (
<p key={idx} className="text-xs text-blue-600"> {issue}</p>
))}
</div>
)}
{docValidation.w9.notes && (
<p className="text-xs text-slate-500 mt-2 italic">{docValidation.w9.notes}</p>
)}
</div>
)}
</div>
{/* COI Upload */}
<div>
<div className="flex items-start justify-between mb-4">
<div>
<h3 className="font-semibold flex items-center gap-2">
<Shield className="w-5 h-5 text-[#0A39DF]" />
Certificate of Insurance (COI) <span className="text-red-500">*</span>
</h3>
<p className="text-sm text-slate-600 mt-1">General liability insurance certificate</p>
</div>
{docValidation.coi && (
<Badge className={docValidation.coi.isValid ? "bg-green-100 text-green-700" : "bg-blue-100 text-blue-700"}>
{docValidation.coi.isValid ? "✅ Validated" : " Review"}
</Badge>
)}
</div>
<DragDropFileUpload
onFileSelect={(file) => 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 && (
<div className="mt-3 p-3 bg-slate-50 rounded-lg">
<p className="text-sm font-medium text-slate-700 mb-1">Validation Results:</p>
{docValidation.coi.expiryDate && (
<p className="text-xs text-slate-600"> Expires: {docValidation.coi.expiryDate}</p>
)}
{docValidation.coi.issues && docValidation.coi.issues.length > 0 && (
<div className="mt-2">
{docValidation.coi.issues.map((issue, idx) => (
<p key={idx} className="text-xs text-blue-600"> {issue}</p>
))}
</div>
)}
{docValidation.coi.notes && (
<p className="text-xs text-slate-500 mt-2 italic">{docValidation.coi.notes}</p>
)}
</div>
)}
</div>
{/* Secretary of State Certificate */}
<div className="border-2 border-red-300 rounded-lg p-6 bg-red-50/30">
<div className="flex items-start justify-between mb-4">
<div>
<h3 className="font-semibold flex items-center gap-2">
<CheckCircle2 className="w-5 h-5 text-[#0A39DF]" />
Secretary of State Certificate <span className="text-red-500">*</span>
</h3>
<p className="text-sm text-slate-600 mt-1">Business registration verification - <strong className="text-red-600">Required</strong></p>
</div>
{docValidation.sos && (
<Badge className={docValidation.sos.isValid || (docValidation.sos.isRegistered && docValidation.sos.autoVerified) ? "bg-green-100 text-green-700" : "bg-blue-100 text-blue-700"}>
{(docValidation.sos.isValid || (docValidation.sos.isRegistered && docValidation.sos.autoVerified)) ? "✅ Validated" : " Review"}
</Badge>
)}
</div>
{/* Auto-Check Button - NOW REQUIRED */}
<div className="mb-4 p-4 bg-blue-50 border-2 border-blue-300 rounded-lg">
<div className="flex items-start justify-between gap-4">
<div className="flex-1">
<p className="text-sm font-semibold text-blue-900 mb-1">🔍 Automatic Verification - <span className="text-red-600">Required</span></p>
<p className="text-xs text-blue-700">
Click "Auto-Check" to verify your business registration with the Secretary of State. This is mandatory for approval.
</p>
</div>
<Button
type="button"
onClick={async () => {
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' ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
Checking...
</>
) : (
<>
<Search className="w-4 h-4 mr-2" />
Auto-Check
</>
)}
</Button>
</div>
</div>
{/* Show auto-verification results */}
{docValidation.sos?.autoVerified && (
<div className="mb-4 p-4 bg-slate-50 rounded-lg border border-slate-200">
<div className="flex items-center gap-2 mb-3">
{docValidation.sos.isRegistered ? (
<CheckCircle2 className="w-5 h-5 text-green-600" />
) : (
<AlertCircle className="w-5 h-5 text-blue-600" />
)}
<p className="font-semibold text-sm">
{docValidation.sos.isRegistered ? "✅ Automatically Verified" : " Verification Results"}
</p>
</div>
<div className="space-y-2 text-sm">
{docValidation.sos.businessName && (
<div className="flex justify-between">
<span className="text-slate-600">Business Name:</span>
<span className="font-medium">{docValidation.sos.businessName}</span>
</div>
)}
{docValidation.sos.registrationState && (
<div className="flex justify-between">
<span className="text-slate-600">State:</span>
<span className="font-medium">{docValidation.sos.registrationState}</span>
</div>
)}
{docValidation.sos.registrationStatus && (
<div className="flex justify-between">
<span className="text-slate-600">Status:</span>
<Badge className={docValidation.sos.registrationStatus === 'Active' || docValidation.sos.registrationStatus === 'Good Standing' ? 'bg-green-100 text-green-700' : 'bg-blue-100 text-blue-700'}>
{docValidation.sos.registrationStatus}
</Badge>
</div>
)}
{docValidation.sos.entityType && (
<div className="flex justify-between">
<span className="text-slate-600">EntityType:</span>
<span className="font-medium">{docValidation.sos.entityType}</span>
</div>
)}
{docValidation.sos.registrationDate && (
<div className="flex justify-between">
<span className="text-slate-600">Registered:</span>
<span className="font-medium">{docValidation.sos.registrationDate}</span>
</div>
)}
</div>
{docValidation.sos.issues && docValidation.sos.issues.length > 0 && (
<div className="mt-3 pt-3 border-t border-slate-200">
<p className="text-xs font-semibold text-blue-700 mb-2">Issues Found:</p>
{docValidation.sos.issues.map((issue, idx) => (
<p key={idx} className="text-xs text-blue-600"> {issue}</p>
))}
</div>
)}
{docValidation.sos.notes && (
<p className="text-xs text-slate-500 mt-3 italic">{docValidation.sos.notes}</p>
)}
</div>
)}
{/* Manual Upload Option with Drag & Drop */}
<div className="border-t border-slate-200 pt-4 mt-4">
<p className="text-sm font-medium text-slate-700 mb-4">Or upload certificate manually for verification:</p>
<DragDropFileUpload
onFileSelect={(file) => 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 && (
<div className="mt-3 p-3 bg-slate-50 rounded-lg">
<p className="text-sm font-medium text-slate-700 mb-1">Document Validation Results:</p>
{docValidation.sos.businessName && (
<p className="text-xs text-slate-600"> Business: {docValidation.sos.businessName}</p>
)}
{docValidation.sos.registrationState && (
<p className="text-xs text-slate-600"> State: {docValidation.sos.registrationState}</p>
)}
{docValidation.sos.expiryDate && (
<p className="text-xs text-slate-600"> Current through: {docValidation.sos.expiryDate}</p>
)}
{docValidation.sos.issues && docValidation.sos.issues.length > 0 && (
<div className="mt-2">
{docValidation.sos.issues.map((issue, idx) => (
<p key={idx} className="text-xs text-blue-600"> {issue}</p>
))}
</div>
)}
{docValidation.sos.notes && (
<p className="text-xs text-slate-500 mt-2 italic">{docValidation.sos.notes}</p>
)}
</div>
)}
</div>
<div className="mt-4 p-3 bg-red-50 rounded-lg border border-red-200">
<p className="text-xs text-red-900 flex items-start gap-2 font-semibold">
<AlertCircle className="w-4 h-4 mt-0.5 flex-shrink-0" />
<span>
<strong>REQUIRED:</strong> This certificate verifies your business is actively registered with your state.
You must either use Auto-Check or upload a valid certificate to proceed.
</span>
</p>
</div>
</div>
{/* NEW: Compliance Attestations Section */}
<div className="border-2 border-blue-300 rounded-lg p-6 bg-blue-50/30 mt-6">
<div className="flex items-center gap-3 mb-4">
<div className="w-10 h-10 bg-blue-600 rounded-lg flex items-center justify-center">
<Shield className="w-5 h-5 text-white" />
</div>
<div>
<h3 className="font-semibold text-lg text-blue-900">Compliance Attestations</h3>
<p className="text-sm text-blue-700">Verify your compliance with employment and safety regulations</p>
</div>
</div>
<div className="space-y-4 bg-white p-6 rounded-lg border-2 border-blue-200">
{/* Background Check Attestation */}
<div className="flex items-start gap-3 p-4 bg-slate-50 rounded-lg border border-slate-200">
<Checkbox
id="background_check"
checked={formData.background_check_attestation}
onCheckedChange={(checked) => setFormData(prev => ({ ...prev, background_check_attestation: checked }))}
className="mt-1 data-[state=checked]:bg-[#0A39DF] data-[state=checked]:border-[#0A39DF]"
/>
<div className="flex-1">
<Label htmlFor="background_check" className="font-semibold cursor-pointer text-slate-900">
Background Check Compliance <span className="text-red-500">*</span>
</Label>
<p className="text-sm text-slate-600 mt-1">
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.
</p>
</div>
</div>
{/* I-9 Compliance Attestation */}
<div className="flex items-start gap-3 p-4 bg-slate-50 rounded-lg border border-slate-200">
<Checkbox
id="i9_compliance"
checked={formData.i9_compliance_attestation}
onCheckedChange={(checked) => setFormData(prev => ({ ...prev, i9_compliance_attestation: checked }))}
className="mt-1 data-[state=checked]:bg-[#0A39DF] data-[state=checked]:border-[#0A39DF]"
/>
<div className="flex-1">
<Label htmlFor="i9_compliance" className="font-semibold cursor-pointer text-slate-900">
I-9 Employment Eligibility Verification <span className="text-red-500">*</span>
</Label>
<p className="text-sm text-slate-600 mt-1">
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.
</p>
</div>
</div>
{/* Legal Compliance Attestation */}
<div className="flex items-start gap-3 p-4 bg-slate-50 rounded-lg border border-slate-200">
<Checkbox
id="legal_compliance"
checked={formData.legal_compliance_attestation}
onCheckedChange={(checked) => setFormData(prev => ({ ...prev, legal_compliance_attestation: checked }))}
className="mt-1 data-[state=checked]:bg-[#0A39DF] data-[state=checked]:border-[#0A39DF]"
/>
<div className="flex-1">
<Label htmlFor="legal_compliance" className="font-semibold cursor-pointer text-slate-900">
General Legal and Regulatory Compliance <span className="text-red-500">*</span>
</Label>
<p className="text-sm text-slate-600 mt-1">
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.
</p>
</div>
</div>
</div>
{/* Warning Message */}
{(!formData.background_check_attestation || !formData.i9_compliance_attestation || !formData.legal_compliance_attestation) && (
<div className="mt-4 p-4 bg-red-50 border-2 border-red-200 rounded-lg flex items-start gap-2">
<AlertCircle className="w-5 h-5 text-red-600 mt-0.5 flex-shrink-0" />
<div>
<p className="text-sm text-red-900 font-semibold">Required Attestations</p>
<p className="text-sm text-red-800 mt-1">
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.
</p>
</div>
</div>
)}
{/* Success Message */}
{formData.background_check_attestation && formData.i9_compliance_attestation && formData.legal_compliance_attestation && (
<div className="mt-4 p-4 bg-green-50 border-2 border-green-200 rounded-lg flex items-center gap-2">
<CheckCircle2 className="w-5 h-5 text-green-600" />
<p className="text-sm text-green-900 font-medium">
✓ All compliance attestations completed. Your commitment to legal compliance is appreciated.
</p>
</div>
)}
</div>
</CardContent>
</Card>
)}
{/* STEP 5: Service Coverage (was Step 4) */}
{currentStep === 5 && (
<Card className="border-2 border-slate-200">
<CardHeader className="bg-gradient-to-r from-slate-50 to-blue-50 border-b">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-12 h-12 bg-[#0A39DF] rounded-xl flex items-center justify-center">
<MapPin className="w-6 h-6 text-white" />
</div>
<div>
<CardTitle className="text-2xl">Service Coverage</CardTitle>
<p className="text-sm text-slate-600 mt-1">Select all cities and regions where you can provide services</p>
</div>
</div>
<Badge className="bg-blue-100 text-blue-700 text-sm px-3 py-1">
{Object.values(formData.selected_cities).reduce((sum, cities) => sum + cities.length, 0)} Cities Selected
</Badge>
</div>
</CardHeader>
<CardContent className="p-6 space-y-6">
{/* Search and Quick Actions */}
<div className="flex gap-3">
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
<Input
placeholder="Search states or cities..."
className="pl-10"
onChange={(e) => {
// 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.
}}
/>
</div>
<Button
variant="outline"
onClick={() => {
// 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
</Button>
</div>
{/* Selected Regions Summary */}
{Object.values(formData.selected_cities).reduce((sum, cities) => sum + cities.length, 0) > 0 && (
<div className="p-4 bg-green-50 border-2 border-green-200 rounded-lg">
<div className="flex items-center justify-between mb-3">
<h4 className="font-semibold text-green-900">Selected Service Areas</h4>
<Button
variant="ghost"
size="sm"
onClick={() => setFormData(prev => ({ ...prev, selected_cities: {} }))}
className="text-red-600 hover:text-red-700 hover:bg-red-50"
>
Clear All
</Button>
</div>
<div className="space-y-2">
{Object.entries(formData.selected_cities).map(([state, cities]) => {
if (cities.length === 0) return null; // Don't show states with no selected cities
return (
<div key={state} className="flex flex-wrap gap-2 items-center">
<Badge className="bg-green-600 text-white font-semibold">
{state}
</Badge>
{cities.map(city => (
<button
key={city}
type="button"
onClick={() => {
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}
<Trash2 className="w-3 h-3 text-red-500" />
</button>
))}
</div>
);
})}
</div>
</div>
)}
{/* State and City Selection */}
<div className="border-2 border-slate-200 rounded-lg p-6 max-h-[500px] overflow-y-auto">
<div className="space-y-6">
{Object.entries(usLocationData).map(([state, cities]) => (
<div key={state} className="border-b border-slate-200 pb-4 last:border-0">
<div className="flex items-center justify-between mb-3">
<h4 className="font-bold text-lg text-slate-900">{state}</h4>
<Button
variant="outline"
size="sm"
onClick={() => {
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"}
</Button>
</div>
<div className="flex flex-wrap gap-2">
{cities.map(city => {
const isSelected = formData.selected_cities[state]?.includes(city);
return (
<button
key={city}
type="button"
onClick={() => {
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 && <CheckCircle2 className="w-3 h-3 inline mr-1" />}
{city}
</button>
);
})}
</div>
</div>
))}
</div>
</div>
{Object.values(formData.selected_cities).reduce((sum, cities) => sum + cities.length, 0) === 0 && (
<div className="text-center py-8 text-slate-500">
<MapPin className="w-12 h-12 mx-auto mb-3 text-slate-300" />
<p className="font-medium">No cities selected yet</p>
<p className="text-sm">Select the cities where you can provide staffing services</p>
</div>
)}
</CardContent>
</Card>
)}
{/* STEP 6: Rate Proposals (was Step 5) */}
{currentStep === 6 && (
<Card className="border-2 border-slate-200">
<CardHeader className="bg-gradient-to-r from-slate-50 to-blue-50 border-b">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-12 h-12 bg-[#0A39DF] rounded-xl flex items-center justify-center">
<DollarSign className="w-6 h-6 text-white" />
</div>
<div>
<CardTitle className="text-2xl">Service Matrix Builder</CardTitle>
<p className="text-sm text-slate-600 mt-1">Tell us where you shine - AI-powered rate analysis</p>
</div>
</div>
<div className="flex items-center gap-4">
<div className="text-right">
<p className="text-xs text-slate-500">Vendor Admin Fee (VA)</p>
<p className="text-xl font-bold text-purple-600">{invite?.vendor_admin_fee || 12}%</p>
<p className="text-[10px] text-slate-400">Set by Procurement</p>
</div>
<Button
type="button"
onClick={() => {
// 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"
>
<Plus className="w-4 h-4 mr-2" />
Add Custom Role
</Button>
</div>
</div>
</CardHeader>
<CardContent className="p-6">
<div className="mb-6 p-4 bg-gradient-to-r from-blue-50 to-purple-50 border border-blue-200 rounded-lg">
<div className="flex items-start gap-3">
<Sparkles className="w-5 h-5 text-purple-600 mt-0.5" />
<div className="flex-1">
<p className="text-sm font-semibold text-slate-900 mb-1">🧠 AI runs in background:</p>
<p className="text-xs text-slate-700">
Benchmarks each entry against KROW's dynamic rate matrix, historic client spend,
and market average for your region and role.
</p>
</div>
</div>
</div>
{/* Minimum Wage Detected */}
{formData.service_address && ( // Changed to service_address
<div className="mb-6 p-4 bg-green-50 border-2 border-green-200 rounded-lg">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<MapPin className="w-5 h-5 text-green-600" />
<div>
<p className="text-sm font-semibold text-green-900">
Local Minimum Wage Detected: ${getMinimumWageFromAddress(formData.service_address)}/hr
</p>
<p className="text-xs text-green-700 mt-1">
Based on your business location. All payrates are pre-populated at or above this minimum. You can adjust them as needed.
</p>
</div>
</div>
<Badge className="bg-green-600 text-white text-sm px-3 py-1">
Auto-Detected
</Badge>
</div>
</div>
)}
{/* Global Controls */}
<div className="mb-6 p-4 bg-slate-50 border-2 border-slate-300 rounded-lg">
<div className="flex items-center justify-between gap-6">
<div className="flex items-center gap-4 flex-1">
<h4 className="font-semibold text-slate-900">Quick Actions:</h4>
{/* Select All / Deselect All */}
<Button
type="button"
variant="outline"
size="sm"
onClick={() => {
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) ? (
<>
<CheckCircle2 className="w-4 h-4 mr-2" />
Deselect All
</>
) : (
<>
<CheckCircle2 className="w-4 h-4 mr-2" />
Select All
</>
)}
</Button>
</div>
{/* Apply Markup to All */}
<div className="flex items-center gap-3">
<Label htmlFor="global_markup" className="text-sm font-semibold whitespace-nowrap">
Markup:
</Label>
<div className="flex items-center gap-2">
<Input
id="global_markup"
type="number"
step="0.1"
placeholder="e.g., 20"
className="w-24 text-center font-semibold"
/>
<span className="text-sm text-slate-500">%</span>
<Button
type="button"
size="sm"
onClick={() => {
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
</Button>
</div>
</div>
</div>
</div>
{/* Enhanced Rate Cards with Location Support */}
<div className="space-y-3">
{(() => {
// 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 (
<Card key={idx} className={`border-2 transition-all ${rate.is_active ? 'border-slate-200 hover:border-[#0A39DF]' : 'border-slate-100 bg-slate-50 opacity-60'}`}>
<CardContent className="p-4">
<div className="flex items-start gap-4">
{/* Toggle */}
<div className="flex items-center pt-2">
<button
type="button"
onClick={() => {
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'}`}
>
<div className={`absolute top-1 w-4 h-4 bg-white rounded-full transition-transform ${rate.is_active ? 'translate-x-7' : 'translate-x-1'}`}></div>
</button>
</div>
{/* Role Info */}
<div className="flex-1 grid grid-cols-6 gap-3 items-center">
{/* Position */}
<div>
<p className="text-xs text-slate-500 mb-1">Position</p>
{rate.is_custom ? (
<Input
value={rate.role_name}
onChange={(e) => {
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}
/>
) : (
<p className="font-semibold text-slate-900 text-sm">{rate.role_name}</p>
)}
<p className="text-[10px] text-slate-400">{rate.category}</p>
</div>
{/* Payrate */}
<div>
<p className="text-xs text-slate-500 mb-1">Payrate</p>
<div className="relative">
<span className="absolute left-2 top-1/2 -translate-y-1/2 text-xs text-slate-400">$</span>
<Input
type="number"
step="0.01"
value={rate.employee_wage}
onChange={(e) => {
const newRates = [...formData.rate_proposals];
const wage = parseFloat(e.target.value) || 0;
newRates[idx].employee_wage = wage;
newRates[idx].client_rate = parseFloat((wage * (1 + newRates[idx].markup_percentage / 100) * (1 + vendorFee / 100)).toFixed(2));
// Also update client rates in location_rates
if (newRates[idx].location_rates) {
newRates[idx].location_rates = Object.fromEntries(
Object.entries(newRates[idx].location_rates).map(([loc, lr]) => [
loc,
{
...lr,
employee_wage: wage, // Update wage for location-specific rate
client_rate: parseFloat((wage * (1 + lr.markup_percentage / 100) * (1 + vendorFee / 100)).toFixed(2))
}
])
);
}
setFormData(prev => ({ ...prev, rate_proposals: newRates }));
}}
className="h-8 text-sm pl-5 font-semibold"
disabled={!rate.is_active}
/>
</div>
</div>
{/* Markup */}
<div>
<p className="text-xs text-slate-500 mb-1">Mark up</p>
<div className="relative">
<Input
type="number"
step="0.1"
value={rate.markup_percentage}
onChange={(e) => {
const newRates = [...formData.rate_proposals];
const markup = parseFloat(e.target.value) || 0;
newRates[idx].markup_percentage = markup;
newRates[idx].client_rate = parseFloat((newRates[idx].employee_wage * (1 + markup / 100) * (1 + vendorFee / 100)).toFixed(2));
// Also update client rates in location_rates
if (newRates[idx].location_rates) {
newRates[idx].location_rates = Object.fromEntries(
Object.entries(newRates[idx].location_rates).map(([loc, lr]) => [
loc,
{
...lr,
markup_percentage: markup, // Update markup for location-specific rate
client_rate: parseFloat((lr.employee_wage * (1 + markup / 100) * (1 + vendorFee / 100)).toFixed(2))
}
])
);
}
setFormData(prev => ({ ...prev, rate_proposals: newRates }));
}}
className="h-8 text-sm font-semibold text-blue-600"
disabled={!rate.is_active}
/>
<span className="absolute right-2 top-1/2 -translate-y-1/2 text-xs text-slate-400">%</span>
</div>
</div>
{/* Vendor Fee % (Read-only) */}
<div>
<p className="text-xs text-slate-500 mb-1">Vendor Fee %</p>
<div className="h-8 flex items-center justify-center bg-purple-50 border border-purple-200 rounded text-sm font-bold text-purple-600">
{vendorFee}%
</div>
</div>
{/* Proposed Bill Rate */}
<div>
<p className="text-xs text-slate-500 mb-1">Proposed Bill Rate</p>
<div className="h-8 flex items-center justify-center bg-green-50 border border-green-200 rounded">
<span className="text-sm font-bold text-green-700">${rate.client_rate}</span>
</div>
</div>
{/* Status */}
<div>
<p className="text-xs text-slate-500 mb-1">Status</p>
<Badge className={`${statusClass} text-xs font-semibold`}>
{statusIcon} {statusText}
</Badge>
</div>
</div>
{/* Actions */}
{rate.is_custom && (
<Button
type="button"
variant="ghost"
size="icon"
onClick={() => {
setFormData(prev => ({
...prev,
rate_proposals: prev.rate_proposals.filter((_, i) => i !== idx)
}));
}}
className="text-red-600 hover:text-red-700 hover:bg-red-50"
>
<Trash2 className="w-4 h-4" />
</Button>
)}
</div>
{/* NEW: Location-Specific Rates Section */}
{rate.is_active && selectedLocations.length > 0 && ( // Show if active and at least one location is selected
<div className="mt-4 pt-4 border-t border-slate-200">
<div className="flex items-center justify-between mb-3">
<h5 className="font-semibold text-sm">Location-Specific Rates</h5>
<Badge variant="outline" className="text-xs">
{selectedLocations.length} Locations Available
</Badge>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
{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 (
<div key={location} className="p-3 bg-slate-50 rounded-lg border border-slate-200">
<p className="text-xs font-semibold text-slate-700 mb-2 truncate">{location}</p>
<div className="grid grid-cols-3 gap-2">
<div>
<Label className="text-[10px]">Payrate</Label>
<div className="relative">
<span className="absolute left-1.5 top-1/2 -translate-y-1/2 text-[10px]">$</span>
<Input
type="number"
step="0.01"
value={locationRate.employee_wage}
onChange={(e) => {
const newWage = parseFloat(e.target.value) || 0;
const newClientRate = parseFloat((newWage * (1 + locationRate.markup_percentage / 100) * (1 + vendorFee / 100)).toFixed(2));
setFormData(prev => {
const newRates = [...prev.rate_proposals];
if (!newRates[idx].location_rates) newRates[idx].location_rates = {};
newRates[idx].location_rates[location] = {
...locationRate,
employee_wage: newWage,
client_rate: newClientRate
};
return { ...prev, rate_proposals: newRates };
});
}}
className="h-7 text-xs pl-4"
/>
</div>
</div>
<div>
<Label className="text-[10px]">Markup</Label>
<Input
type="number"
step="0.1"
value={locationRate.markup_percentage}
onChange={(e) => {
const newMarkup = parseFloat(e.target.value) || 0;
const newClientRate = parseFloat((locationRate.employee_wage * (1 + newMarkup / 100) * (1 + vendorFee / 100)).toFixed(2));
setFormData(prev => {
const newRates = [...prev.rate_proposals];
if (!newRates[idx].location_rates) newRates[idx].location_rates = {};
newRates[idx].location_rates[location] = {
...locationRate,
markup_percentage: newMarkup,
client_rate: newClientRate
};
return { ...prev, rate_proposals: newRates };
});
}}
className="h-7 text-xs"
/>
</div>
<div>
<Label className="text-[10px]">Bill Rate</Label>
<div className="h-7 flex items-center justify-center bg-green-50 border border-green-200 rounded text-xs font-bold text-green-700">
${locationRate.client_rate}
</div>
</div>
</div>
</div>
);
})}
</div>
<Button
type="button"
variant="outline"
size="sm"
className="mt-3 w-full text-xs border-slate-300 hover:bg-slate-50"
onClick={() => {
// 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
</Button>
</div>
)}
{/* Expanded Details - Visual Breakdown */}
{rate.is_active && (
<div className="mt-4 pt-4 border-t border-slate-200">
<div className="grid grid-cols-3 gap-4">
{/* Cost Breakdown Bar */}
<div className="col-span-2">
<p className="text-xs font-semibold text-slate-700 mb-2">Cost Breakdown</p>
<div className="flex items-center gap-2 mb-2">
<div className="flex-1 bg-slate-200 rounded-full h-8 overflow-hidden flex text-xs font-semibold">
<div
className="bg-green-500 flex items-center justify-center text-white"
style={{ width: `${(rate.employee_wage / rate.client_rate) * 100}%` }}
title={`Payrate: $${rate.employee_wage}`}
>
${rate.employee_wage}
</div>
<div
className="bg-blue-500 flex items-center justify-center text-white"
style={{ width: `${((rate.employee_wage * rate.markup_percentage / 100) / rate.client_rate) * 100}%` }}
title={`Markup: ${rate.markup_percentage}%`}
>
{rate.markup_percentage}%
</div>
<div
className="bg-purple-500 flex items-center justify-center text-white"
style={{ width: `${((rate.employee_wage * (1 + rate.markup_percentage / 100) * vendorFee / 100) / rate.client_rate) * 100}%` }}
title={`VA Fee: ${vendorFee}%`}
>
{vendorFee}%
</div>
</div>
</div>
<div className="flex justify-between text-[10px] text-slate-600">
<span>Payrate: ${rate.employee_wage}/hr</span>
<span>Markup: {rate.markup_percentage}% (+${(rate.employee_wage * rate.markup_percentage / 100).toFixed(2)})</span>
<span>VA: {vendorFee}% (+${(rate.employee_wage * (1 + rate.markup_percentage / 100) * vendorFee / 100).toFixed(2)})</span>
</div>
</div>
{/* AI Insights */}
<div className="bg-slate-50 rounded-lg p-3">
<p className="text-xs font-semibold text-slate-700 mb-2">💡 AI Insights</p>
<div className="space-y-1 text-[10px]">
<div className="flex justify-between">
<span className="text-slate-600">Market Avg:</span>
<span className="font-semibold">${marketAverage.toFixed(2)}/hr</span>
</div>
<div className="flex justify-between">
<span className="text-slate-600">Your Rate:</span>
<span className="font-semibold text-[#0A39DF]">${rate.client_rate}/hr</span>
</div>
<div className="flex justify-between">
<span className="text-slate-600">Difference:</span>
<span className={`font-semibold ${rate.client_rate < marketAverage ? 'text-green-600' : 'text-red-600'}`}>
{rate.client_rate < marketAverage ? '-' : '+'}${Math.abs(rate.client_rate - marketAverage).toFixed(2)}
</span>
</div>
<div className="pt-2 mt-2 border-t border-slate-200">
<span className="text-slate-600">Fill Rate:</span>
<span className={`ml-1 font-bold ${status === 'competitive' ? 'text-green-600' : status === 'market' ? 'text-yellow-600' : 'text-red-600'}`}>
{status === 'competitive' ? '95%' : status === 'market' ? '75%' : '45%'}/yr
</span>
</div>
</div>
</div>
</div>
{/* Status Message */}
{status === 'competitive' && (
<div className="mt-3 p-2 bg-green-50 border border-green-200 rounded text-xs text-green-900">
✓ <strong>Good Deal:</strong> Your rate is {((1 - rate.client_rate / marketAverage) * 100).toFixed(0)}% below market average.
Highly competitive with predicted 95% fill rate.
</div>
)}
{status === 'market' && (
<div className="mt-3 p-2 bg-blue-50 border border-blue-200 rounded text-xs text-blue-900">
~ <strong>At Market:</strong> Your rate aligns with market average.
Competitive with predicted 75% fill rate.
</div>
)}
{status === 'risk' && (
<div className="mt-3 p-2 bg-red-50 border border-red-200 rounded text-xs text-red-900 flex items-start gap-2">
<AlertCircle className="w-4 h-4 mt-0.5 flex-shrink-0" />
<div>
<strong>! Risk Zone:</strong> 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.
</div>
</div>
)}
</div>
)}
</CardContent>
</Card>
);
})}
</div>
{/* Summary Stats */}
<div className="mt-6 grid grid-cols-4 gap-4">
<Card className="border-green-200 bg-green-50">
<CardContent className="p-4 text-center">
<p className="text-2xl font-bold text-green-700">
{formData.rate_proposals.filter(r => r.is_active && r.client_rate < (r.employee_wage * 1.35 * 1.05)).length}
</p>
<p className="text-xs text-green-600 font-medium mt-1"> Good Deals</p>
</CardContent>
</Card>
<Card className="border-blue-200 bg-blue-50">
<CardContent className="p-4 text-center">
<p className="text-2xl font-bold text-blue-700">
{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}
</p>
<p className="text-xs text-blue-600 font-medium mt-1">~ At Market</p>
</CardContent>
</Card>
<Card className="border-red-200 bg-red-50">
<CardContent className="p-4 text-center">
<p className="text-2xl font-bold text-red-700">
{formData.rate_proposals.filter(r => r.is_active && r.client_rate > (r.employee_wage * 1.35 * 1.15)).length}
</p>
<p className="text-xs text-red-600 font-medium mt-1">! Risk Zone</p>
</CardContent>
</Card>
<Card className="border-slate-200 bg-slate-50">
<CardContent className="p-4 text-center">
<p className="text-2xl font-bold text-slate-700">
{formData.rate_proposals.filter(r => r.is_active).length}
</p>
<p className="text-xs text-slate-600 font-medium mt-1">Active Roles</p>
</CardContent>
</Card>
</div>
</CardContent>
</Card>
)}
{/* STEP 7: AI Intelligence (was Step 6) */}
{currentStep === 7 && (
<Card className="border-2 border-slate-200">
<CardHeader className="bg-gradient-to-r from-purple-50 to-pink-50 border-b">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-12 h-12 bg-gradient-to-r from-purple-600 to-pink-600 rounded-xl flex items-center justify-center">
<Sparkles className="w-6 h-6 text-white" />
</div>
<div>
<CardTitle className="text-2xl">AI Intelligence</CardTitle>
<p className="text-sm text-slate-600 mt-1">Market insights & recommendations</p>
</div>
</div>
{!aiInsights && (
<Button
onClick={generateAIInsights}
className="bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700"
>
<Sparkles className="w-4 h-4 mr-2" />
Generate Insights
</Button>
)}
</div>
</CardHeader>
<CardContent className="p-6">
{!aiInsights ? (
<div className="text-center py-12">
<Sparkles className="w-16 h-16 mx-auto mb-3 text-purple-300" />
<p className="text-slate-600 mb-2">Ready to analyze your application</p>
<p className="text-sm text-slate-500">Get AI-powered insights on your competitive positioning</p>
</div>
) : (
<div className="space-y-6">
{/* Overall Score */}
<div className="p-6 bg-gradient-to-r from-purple-50 to-pink-50 rounded-lg border border-purple-200">
<div className="flex items-center justify-between mb-4">
<h3 className="font-semibold text-lg">Overall Assessment</h3>
<div className="text-4xl font-bold text-purple-600">
{aiInsights.overall_score}/100
</div>
</div>
<p className="text-sm text-slate-700">{aiInsights.competitive_position}</p>
</div>
{/* Strengths */}
{aiInsights.strengths && aiInsights.strengths.length > 0 && (
<div>
<h4 className="font-semibold mb-3 flex items-center gap-2">
<CheckCircle2 className="w-5 h-5 text-green-600" />
Strengths
</h4>
<div className="space-y-2">
{aiInsights.strengths.map((strength, idx) => (
<div key={idx} className="p-3 bg-green-50 border border-green-200 rounded-lg">
<p className="text-sm text-green-900"> {strength}</p>
</div>
))}
</div>
</div>
)}
{/* Recommendations */}
{aiInsights.recommendations && aiInsights.recommendations.length > 0 && (
<div>
<h4 className="font-semibold mb-3 flex items-center gap-2">
<TrendingUp className="w-5 h-5 text-blue-600" />
Recommendations
</h4>
<div className="space-y-2">
{aiInsights.recommendations.map((rec, idx) => (
<div key={idx} className="p-3 bg-blue-50 border border-blue-200 rounded-lg">
<p className="text-sm text-blue-900">💡 {rec}</p>
</div>
))}
</div>
</div>
)}
{/* Analysis Details */}
<div className="grid grid-cols-2 gap-4">
<div className="p-4 bg-slate-50 rounded-lg">
<h5 className="font-semibold text-sm mb-2">Market Alignment</h5>
<p className="text-xs text-slate-600">{aiInsights.market_alignment}</p>
</div>
<div className="p-4 bg-slate-50 rounded-lg">
<h5 className="font-semibold text-sm mb-2">Pricing Analysis</h5>
<p className="text-xs text-slate-600">{aiInsights.pricing_analysis}</p>
</div>
</div>
</div>
)}
</CardContent>
</Card>
)}
{/* Navigation Buttons */}
{currentStep > 0 && (
<div className="flex items-center justify-between mt-6">
<Button
variant="outline"
onClick={handleBack}
disabled={currentStep === 1}
className="border-slate-300 hover:bg-slate-50"
>
<ArrowLeft className="w-4 h-4 mr-2" />
Back
</Button>
{currentStep < steps.length - 1 ? (
<Button
onClick={handleNext}
className="bg-[#0A39DF] hover:bg-[#0A39DF]/90 text-white"
>
Next Step
<ArrowRight className="w-4 h-4 mr-2" />
</Button>
) : (
<Button
onClick={handleSubmit}
disabled={submitApplicationMutation.isPending}
className="bg-gradient-to-r from-green-600 to-emerald-600 hover:from-green-700 hover:to-emerald-700"
>
{submitApplicationMutation.isPending ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
Submitting...
</>
) : (
<>
<CheckCircle2 className="w-4 h-4 mr-2" />
Submit Application
</>
)}
</Button>
)}
</div>
)}
</div>
</div>
);
}