sync: Auto-sync from ACG-M-L5090 at 2026-03-10 19:11:00

Synced files:
- Quote wizard frontend (all components, hooks, types, config)
- API updates (config, models, routers, schemas, services)
- Client work (bg-builders, gurushow)
- Scripts (BGB Lesley termination, CIPP, Datto, migration)
- Temp files (Bardach contacts, VWP investigation, misc)
- Credentials and session logs
- Email service, PHP API, session logs

Machine: ACG-M-L5090
Timestamp: 2026-03-10 19:11:00

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-10 19:59:08 -07:00
parent 84fce5a621
commit af72a12e3e
168 changed files with 879909 additions and 1243 deletions

View File

@@ -60,27 +60,50 @@ export function Step4VoIP({
transition={{ duration: 0.3 }}
className="space-y-6"
>
{/* Service Explainer */}
<div className="space-y-2">
<p className="text-sm sm:text-base text-gray-500 leading-relaxed">
<strong className="text-[#333d49]">VoIP (Voice over IP)</strong> replaces traditional
phone lines with a modern cloud-based phone system. Your calls travel over the internet,
which means lower costs, more features, and the flexibility to take calls from your
desk phone, computer, or mobile app &mdash; anywhere with an internet connection.
</p>
<p className="text-sm text-gray-400 leading-relaxed">
Every plan includes unlimited local and long-distance calling, auto-attendant (press 1
for sales, etc.), voicemail-to-email, call forwarding, and the ability to keep your
existing phone numbers. Higher tiers add call recording, analytics, CRM integrations,
and video conferencing. We can also provide desk phones and headsets as a purchase or
monthly rental.
</p>
</div>
{/* VoIP Toggle */}
<div className="bg-gray-50 rounded-lg p-5">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<Phone className="w-6 h-6 text-[#fe7400]" />
<div>
<h3 className="font-semibold text-[#333d49]">Do you need business phones?</h3>
<p className="text-sm text-gray-500">
<div className="bg-[#f8f9fb] rounded-xl p-4 sm:p-5">
<div className="flex items-center justify-between gap-3">
<div className="flex items-center gap-3 min-w-0">
<div className="w-9 h-9 sm:w-10 sm:h-10 rounded-xl bg-[#fe7400]/8 flex items-center justify-center flex-shrink-0">
<Phone className="w-4 h-4 sm:w-5 sm:h-5 text-[#fe7400]" />
</div>
<div className="min-w-0">
<h3 className="font-bold text-[#333d49] text-sm sm:text-base"
style={{ fontFamily: "'Plus Jakarta Sans', sans-serif" }}>
Do you need business phones?
</h3>
<p className="text-xs sm:text-sm text-gray-400">
Modern VoIP phone system with advanced features
</p>
</div>
</div>
<label className="relative inline-flex items-center cursor-pointer">
<label className="relative inline-flex items-center cursor-pointer flex-shrink-0">
<input
type="checkbox"
checked={voipSelection.enabled}
onChange={(e) => onSetVoIPEnabled(e.target.checked)}
className="sr-only peer"
/>
<div className="w-14 h-7 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-[#fe7400]/20 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-6 after:w-6 after:transition-all peer-checked:bg-[#fe7400]"></div>
<span className="ml-3 text-sm font-medium text-gray-700">
<div className="w-14 h-7 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-[#fe7400]/10 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-6 after:w-6 after:transition-all peer-checked:bg-[#fe7400]"></div>
<span className="ml-3 text-sm font-semibold text-gray-500"
style={{ fontFamily: "'Plus Jakarta Sans', sans-serif" }}>
{voipSelection.enabled ? 'Yes' : 'No'}
</span>
</label>
@@ -97,19 +120,22 @@ export function Step4VoIP({
className="space-y-6"
>
{/* User Count */}
<div className="flex items-center gap-4 p-4 border border-gray-200 rounded-lg">
<label className="text-sm font-medium text-[#333d49]">Number of phone users:</label>
<div className="flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-4 p-4 border border-gray-200/80 rounded-xl bg-white shadow-card">
<label className="text-sm font-medium text-[#333d49]"
style={{ fontFamily: "'Plus Jakarta Sans', sans-serif" }}>
Number of phone users:
</label>
<Input
type="number"
min={1}
value={voipSelection.userCount}
onChange={(e) => onSetVoIPUserCount(parseInt(e.target.value, 10) || 1)}
className="w-24"
className="w-full sm:w-24"
/>
</div>
{/* Tier Selection */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3 sm:gap-4">
{voipTiers.map((tier, index) => {
const isSelected = voipSelection.tierId === tier.id;
const monthlyPrice = tier.pricePerUser * voipSelection.userCount;
@@ -120,43 +146,48 @@ export function Step4VoIP({
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.1 }}
whileHover={{ y: -4 }}
whileHover={{ y: -3 }}
>
<Card
variant={isSelected ? 'highlighted' : tier.recommended ? 'elevated' : 'default'}
variant={isSelected ? 'highlighted' : 'default'}
padding="none"
className={`relative overflow-hidden cursor-pointer h-full ${
tier.recommended && !isSelected ? 'ring-2 ring-[#333d49]' : ''
tier.recommended && !isSelected ? 'ring-2 ring-[#fe7400]/30' : ''
}`}
onClick={() => onSetVoIPTier(tier.id)}
>
{tier.recommended && (
<div className="absolute top-0 right-0">
<div className="bg-[#fe7400] text-white text-xs font-semibold px-2 py-1 rounded-bl-lg">
Popular
</div>
<div className="bg-gradient-accent text-white text-[11px] font-bold px-3 py-1.5 text-center uppercase tracking-wider"
style={{ fontFamily: "'Plus Jakarta Sans', sans-serif" }}>
Popular
</div>
)}
<div className="p-4">
<h3 className="text-lg font-semibold text-[#333d49]">{tier.name}</h3>
<p className="text-xs text-gray-500 mb-3">{tier.description}</p>
<h3 className="text-base font-bold text-[#333d49]"
style={{ fontFamily: "'Plus Jakarta Sans', sans-serif" }}>
{tier.name}
</h3>
<p className="text-xs text-gray-400 mb-3">{tier.description}</p>
<div className="mb-3">
<span className="text-xl font-bold text-[#333d49]">
<span className="text-xl font-bold text-[#333d49]"
style={{ fontFamily: "'Plus Jakarta Sans', sans-serif" }}>
{formatCurrency(monthlyPrice)}
</span>
<span className="text-gray-500 text-xs">/mo</span>
<span className="text-gray-400 text-xs">/mo</span>
<p className="text-xs text-gray-400">
{formatCurrency(tier.pricePerUser)}/user
</p>
</div>
<ul className="space-y-1 mb-4">
<ul className="space-y-1.5 mb-4">
{tier.features.slice(0, 3).map((feature, idx) => (
<li key={idx} className="flex items-start gap-1.5 text-xs">
<Check className="w-3 h-3 text-[#fe7400] mt-0.5 flex-shrink-0" />
<span className="text-gray-600">{feature}</span>
<li key={idx} className="flex items-start gap-2 text-xs">
<div className="w-3.5 h-3.5 rounded-full bg-[#fe7400]/10 flex items-center justify-center flex-shrink-0 mt-0.5">
<Check className="w-2 h-2 text-[#fe7400]" strokeWidth={3} />
</div>
<span className="text-gray-500">{feature}</span>
</li>
))}
</ul>
@@ -176,19 +207,21 @@ export function Step4VoIP({
</div>
{/* Hardware Section */}
<div className="border border-gray-200 rounded-lg overflow-hidden">
<div className="border border-gray-200/80 rounded-xl overflow-hidden bg-white shadow-card">
<button
type="button"
onClick={() => setShowHardware(!showHardware)}
className="w-full flex items-center justify-between p-4 bg-gray-50 hover:bg-gray-100 transition-colors"
className="w-full flex items-center justify-between p-4 bg-[#f8f9fb] hover:bg-gray-100 transition-colors"
>
<div className="flex items-center gap-3">
<Headphones className="w-5 h-5 text-[#fe7400]" />
<span className="font-medium text-[#333d49]">
<span className="font-semibold text-[#333d49]"
style={{ fontFamily: "'Plus Jakarta Sans', sans-serif" }}>
Phone Hardware (Optional)
</span>
</div>
<span className="text-sm text-gray-500">
<span className="text-sm text-gray-400 font-medium"
style={{ fontFamily: "'Plus Jakarta Sans', sans-serif" }}>
{showHardware ? 'Hide' : 'Show'} options
</span>
</button>
@@ -209,81 +242,84 @@ export function Step4VoIP({
return (
<div
key={hardware.id}
className={`p-4 rounded-lg border-2 transition-all ${
className={`p-4 rounded-xl border-2 transition-all duration-200 ${
isSelected
? 'border-[#fe7400] bg-[#fe7400]/5'
: 'border-gray-200'
}`}
>
<div className="flex items-start justify-between">
<div className="flex-1">
<h4 className="font-medium text-[#333d49]">{hardware.name}</h4>
<p className="text-sm text-gray-500">{hardware.description}</p>
<div className="flex gap-4 mt-2 text-sm">
<span className="text-[#333d49]">
Buy: <strong>{formatCurrency(hardware.oneTimePrice)}</strong>
<div className="space-y-3">
<div>
<h4 className="font-semibold text-[#333d49] text-sm sm:text-base"
style={{ fontFamily: "'Plus Jakarta Sans', sans-serif" }}>
{hardware.name}
</h4>
<p className="text-xs sm:text-sm text-gray-400">{hardware.description}</p>
<div className="flex gap-3 sm:gap-4 mt-2 text-xs sm:text-sm">
<span className="text-gray-500">
Buy: <strong className="text-[#333d49]">{formatCurrency(hardware.oneTimePrice)}</strong>
</span>
<span className="text-[#333d49]">
Rent: <strong>{formatCurrency(hardware.monthlyRental)}</strong>/mo
<span className="text-gray-500">
Rent: <strong className="text-[#333d49]">{formatCurrency(hardware.monthlyRental)}</strong>/mo
</span>
</div>
</div>
{isSelected ? (
<div className="flex items-center gap-3">
{/* Rental Toggle */}
<div className="flex items-center gap-2">
<div className="flex flex-wrap items-center gap-2 sm:gap-3">
<div className="flex items-center gap-1.5">
<button
type="button"
onClick={() => onAddHardware(hardware.id, selection.quantity, false)}
className={`px-2 py-1 text-xs rounded ${
className={`px-2.5 py-1.5 text-xs rounded-lg font-semibold transition-colors ${
!selection.isRental
? 'bg-[#fe7400] text-white'
: 'bg-gray-200 text-gray-600'
: 'bg-gray-100 text-gray-500 hover:bg-gray-200'
}`}
style={{ fontFamily: "'Plus Jakarta Sans', sans-serif" }}
>
Buy
</button>
<button
type="button"
onClick={() => onAddHardware(hardware.id, selection.quantity, true)}
className={`px-2 py-1 text-xs rounded ${
className={`px-2.5 py-1.5 text-xs rounded-lg font-semibold transition-colors ${
selection.isRental
? 'bg-[#fe7400] text-white'
: 'bg-gray-200 text-gray-600'
: 'bg-gray-100 text-gray-500 hover:bg-gray-200'
}`}
style={{ fontFamily: "'Plus Jakarta Sans', sans-serif" }}
>
Rent
</button>
</div>
{/* Quantity */}
<div className="flex items-center gap-2 border border-gray-300 rounded-lg">
<div className="flex items-center gap-1 border border-gray-200 rounded-lg">
<button
type="button"
onClick={() => handleQuantityChange(hardware.id, -1)}
className="p-2 hover:bg-gray-100 rounded-l-lg"
className="p-2 hover:bg-gray-50 rounded-l-lg transition-colors"
disabled={selection.quantity <= 1}
>
<Minus className="w-4 h-4" />
<Minus className="w-3.5 h-3.5" />
</button>
<span className="w-8 text-center font-medium">
<span className="w-8 text-center font-semibold text-sm"
style={{ fontFamily: "'Plus Jakarta Sans', sans-serif" }}>
{selection.quantity}
</span>
<button
type="button"
onClick={() => handleQuantityChange(hardware.id, 1)}
className="p-2 hover:bg-gray-100 rounded-r-lg"
className="p-2 hover:bg-gray-50 rounded-r-lg transition-colors"
>
<Plus className="w-4 h-4" />
<Plus className="w-3.5 h-3.5" />
</button>
</div>
{/* Remove */}
<button
type="button"
onClick={() => onRemoveHardware(hardware.id)}
className="p-2 text-red-500 hover:bg-red-50 rounded-lg"
className="p-2 text-red-400 hover:text-red-500 hover:bg-red-50 rounded-lg transition-colors ml-auto"
>
<X className="w-4 h-4" />
</button>
@@ -317,21 +353,29 @@ export function Step4VoIP({
{/* Info */}
<ExpandableInfo title="VoIP Features & Benefits">
<ul className="space-y-2">
<li className="flex items-start gap-2">
<Check className="w-4 h-4 text-[#fe7400] mt-0.5 flex-shrink-0" />
<ul className="space-y-2.5">
<li className="flex items-start gap-2.5">
<div className="w-4 h-4 rounded-full bg-[#fe7400]/10 flex items-center justify-center flex-shrink-0 mt-0.5">
<Check className="w-2.5 h-2.5 text-[#fe7400]" strokeWidth={3} />
</div>
<span>Unlimited local and long-distance calling</span>
</li>
<li className="flex items-start gap-2">
<Check className="w-4 h-4 text-[#fe7400] mt-0.5 flex-shrink-0" />
<li className="flex items-start gap-2.5">
<div className="w-4 h-4 rounded-full bg-[#fe7400]/10 flex items-center justify-center flex-shrink-0 mt-0.5">
<Check className="w-2.5 h-2.5 text-[#fe7400]" strokeWidth={3} />
</div>
<span>Mobile apps for iOS and Android - take calls anywhere</span>
</li>
<li className="flex items-start gap-2">
<Check className="w-4 h-4 text-[#fe7400] mt-0.5 flex-shrink-0" />
<li className="flex items-start gap-2.5">
<div className="w-4 h-4 rounded-full bg-[#fe7400]/10 flex items-center justify-center flex-shrink-0 mt-0.5">
<Check className="w-2.5 h-2.5 text-[#fe7400]" strokeWidth={3} />
</div>
<span>Auto-attendant and professional voicemail</span>
</li>
<li className="flex items-start gap-2">
<Check className="w-4 h-4 text-[#fe7400] mt-0.5 flex-shrink-0" />
<li className="flex items-start gap-2.5">
<div className="w-4 h-4 rounded-full bg-[#fe7400]/10 flex items-center justify-center flex-shrink-0 mt-0.5">
<Check className="w-2.5 h-2.5 text-[#fe7400]" strokeWidth={3} />
</div>
<span>Keep your existing phone numbers</span>
</li>
</ul>
@@ -339,18 +383,19 @@ export function Step4VoIP({
{/* Totals */}
<div className="space-y-3">
<div className="bg-[#333d49] text-white rounded-lg p-5 flex items-center justify-between">
<span className="text-lg">VoIP Monthly Total</span>
<span className="text-3xl font-bold">
<div className="bg-gradient-dark text-white rounded-xl p-4 sm:p-5 flex items-center justify-between gap-3">
<span className="text-sm sm:text-base font-medium opacity-90">VoIP Monthly Total</span>
<span className="text-2xl sm:text-3xl font-bold whitespace-nowrap" style={{ fontFamily: "'Plus Jakarta Sans', sans-serif" }}>
{formatCurrency(getVoIPMonthly())}
<span className="text-lg font-normal opacity-75">/month</span>
<span className="text-xs sm:text-sm font-medium opacity-60 ml-1">/mo</span>
</span>
</div>
{getVoIPOneTime() > 0 && (
<div className="bg-gray-100 rounded-lg p-4 flex items-center justify-between">
<span className="text-gray-700">Hardware Purchase (One-Time)</span>
<span className="text-xl font-bold text-[#333d49]">
<div className="bg-[#f8f9fb] rounded-xl p-4 flex items-center justify-between border border-gray-200/80">
<span className="text-gray-500 font-medium">Hardware Purchase (One-Time)</span>
<span className="text-xl font-bold text-[#333d49]"
style={{ fontFamily: "'Plus Jakarta Sans', sans-serif" }}>
{formatCurrency(getVoIPOneTime())}
</span>
</div>
@@ -364,10 +409,10 @@ export function Step4VoIP({
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="text-center py-8 text-gray-500"
className="text-center py-12 text-gray-400"
>
<Phone className="w-12 h-12 mx-auto mb-3 opacity-30" />
<p>You can always add VoIP services later.</p>
<Phone className="w-12 h-12 mx-auto mb-3 opacity-20" />
<p className="text-sm">You can always add VoIP services later.</p>
</motion.div>
)}
</motion.div>