- Fix calculateQuote() to respect serviceInterests flags - Only include GPS/Support costs when user has enabled them - Update Step6Summary to conditionally render service sections - Add sender display name (Arizona Computer Guru) to emails - Add reply-to address (admin@azcomputerguru.com) - Fixes phantom $380 support charge appearing in totals Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
735 lines
22 KiB
TypeScript
735 lines
22 KiB
TypeScript
import { useState, useCallback, useMemo, useEffect, useRef } from 'react';
|
|
import type {
|
|
QuoteData,
|
|
QuoteResult,
|
|
QuoteBreakdown,
|
|
CompanyInfo,
|
|
GPSSelection,
|
|
SupportSelection,
|
|
VoIPSelection,
|
|
WebHostingSelection,
|
|
EmailSelection,
|
|
ContactInfo,
|
|
GPSTierId,
|
|
SupportPlanId,
|
|
BlockTimeId,
|
|
VoIPTierId,
|
|
WebHostingTierId,
|
|
EmailTierId,
|
|
EmailProvider,
|
|
Industry,
|
|
ContactPreference,
|
|
ClientType,
|
|
ServiceInterests,
|
|
} from '@/types/quote';
|
|
import {
|
|
gpsTiers,
|
|
equipmentMonitoring,
|
|
supportPlans,
|
|
blockTimeOptions,
|
|
voipTiers,
|
|
voipHardware,
|
|
webHostingTiers,
|
|
emailTiers,
|
|
} from '@/lib/pricing-data';
|
|
|
|
const DRAFT_STORAGE_KEY = 'quote-wizard-draft';
|
|
|
|
/**
|
|
* Load saved draft from localStorage if available.
|
|
* Returns partial state keyed by section, or null if nothing saved.
|
|
*/
|
|
function loadDraft(): {
|
|
clientType?: ClientType;
|
|
serviceInterests?: ServiceInterests;
|
|
company?: CompanyInfo;
|
|
gps?: GPSSelection;
|
|
support?: SupportSelection;
|
|
voip?: VoIPSelection;
|
|
webHosting?: WebHostingSelection;
|
|
email?: EmailSelection;
|
|
contact?: ContactInfo;
|
|
accessToken?: string;
|
|
} | null {
|
|
try {
|
|
const raw = localStorage.getItem(DRAFT_STORAGE_KEY);
|
|
if (!raw) return null;
|
|
return JSON.parse(raw);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initial state values
|
|
*/
|
|
const initialClientType: ClientType = 'company';
|
|
|
|
const initialServiceInterests: ServiceInterests = {
|
|
gps: true,
|
|
support: true,
|
|
voip: false,
|
|
webHosting: false,
|
|
email: false,
|
|
};
|
|
|
|
const initialCompanyInfo: CompanyInfo = {
|
|
name: '',
|
|
endpointCount: 10,
|
|
industry: '',
|
|
notes: '',
|
|
};
|
|
|
|
const initialGPSSelection: GPSSelection = {
|
|
tierId: 'pro',
|
|
endpointCount: 10,
|
|
includeEquipment: false,
|
|
equipmentDeviceCount: 0,
|
|
};
|
|
|
|
const initialSupportSelection: SupportSelection = {
|
|
planId: 'standard',
|
|
useBlockTime: false,
|
|
blockTimeId: null,
|
|
};
|
|
|
|
const initialVoIPSelection: VoIPSelection = {
|
|
enabled: false,
|
|
tierId: 'voip-standard',
|
|
userCount: 0,
|
|
hardware: [],
|
|
};
|
|
|
|
const initialWebHostingSelection: WebHostingSelection = {
|
|
enabled: false,
|
|
tierId: 'hosting-business',
|
|
};
|
|
|
|
const initialEmailSelection: EmailSelection = {
|
|
enabled: false,
|
|
provider: 'm365',
|
|
tierId: 'm365-standard',
|
|
mailboxCount: 0,
|
|
};
|
|
|
|
const initialContactInfo: ContactInfo = {
|
|
name: '',
|
|
email: '',
|
|
phone: '',
|
|
companyName: '',
|
|
currentITSituation: '',
|
|
contactPreference: 'email',
|
|
agreedToTerms: false,
|
|
};
|
|
|
|
/**
|
|
* Hook return type
|
|
*/
|
|
export interface UseQuoteReturn {
|
|
quoteData: QuoteData;
|
|
quoteResult: QuoteResult | null;
|
|
|
|
// Client type & service interests
|
|
setClientType: (type: ClientType) => void;
|
|
setServiceInterest: (service: keyof ServiceInterests, enabled: boolean) => void;
|
|
|
|
// Company updates
|
|
updateCompany: (data: Partial<CompanyInfo>) => void;
|
|
setEndpointCount: (count: number) => void;
|
|
setIndustry: (industry: Industry | '') => void;
|
|
|
|
// GPS updates
|
|
updateGPS: (data: Partial<GPSSelection>) => void;
|
|
setGPSTier: (tierId: GPSTierId) => void;
|
|
setEquipmentEnabled: (enabled: boolean) => void;
|
|
setEquipmentCount: (count: number) => void;
|
|
|
|
// Support updates
|
|
updateSupport: (data: Partial<SupportSelection>) => void;
|
|
setSupportPlan: (planId: SupportPlanId) => void;
|
|
setBlockTimeEnabled: (enabled: boolean) => void;
|
|
setBlockTime: (blockTimeId: BlockTimeId) => void;
|
|
|
|
// VoIP updates
|
|
updateVoIP: (data: Partial<VoIPSelection>) => void;
|
|
setVoIPEnabled: (enabled: boolean) => void;
|
|
setVoIPTier: (tierId: VoIPTierId) => void;
|
|
setVoIPUserCount: (count: number) => void;
|
|
addHardware: (hardwareId: string, quantity: number, isRental: boolean) => void;
|
|
removeHardware: (hardwareId: string) => void;
|
|
updateHardwareQuantity: (hardwareId: string, quantity: number) => void;
|
|
|
|
// Web Hosting updates
|
|
updateWebHosting: (data: Partial<WebHostingSelection>) => void;
|
|
setWebHostingEnabled: (enabled: boolean) => void;
|
|
setWebHostingTier: (tierId: WebHostingTierId) => void;
|
|
|
|
// Email updates
|
|
updateEmail: (data: Partial<EmailSelection>) => void;
|
|
setEmailEnabled: (enabled: boolean) => void;
|
|
setEmailProvider: (provider: EmailProvider) => void;
|
|
setEmailTier: (tierId: EmailTierId) => void;
|
|
setMailboxCount: (count: number) => void;
|
|
|
|
// Contact updates
|
|
updateContact: (data: Partial<ContactInfo>) => void;
|
|
setContactPreference: (preference: ContactPreference) => void;
|
|
setAgreedToTerms: (agreed: boolean) => void;
|
|
|
|
// Calculations
|
|
calculateQuote: () => QuoteResult;
|
|
getGPSMonthly: () => number;
|
|
getSupportMonthly: () => number;
|
|
getVoIPMonthly: () => number;
|
|
getWebHostingMonthly: () => number;
|
|
getEmailMonthly: () => number;
|
|
getSupportBlockTimeOneTime: () => number;
|
|
getVoIPOneTime: () => number;
|
|
|
|
// Reset
|
|
resetQuote: () => void;
|
|
}
|
|
|
|
/**
|
|
* Quote calculation and state management hook
|
|
*/
|
|
export function useQuote(): UseQuoteReturn {
|
|
const draft = useRef(loadDraft());
|
|
|
|
const [clientType, setClientType] = useState<ClientType>(draft.current?.clientType ?? initialClientType);
|
|
const [serviceInterests, setServiceInterests] = useState<ServiceInterests>(draft.current?.serviceInterests ?? initialServiceInterests);
|
|
const [company, setCompany] = useState<CompanyInfo>(draft.current?.company ?? initialCompanyInfo);
|
|
const [gps, setGPS] = useState<GPSSelection>(draft.current?.gps ?? initialGPSSelection);
|
|
const [support, setSupport] = useState<SupportSelection>(draft.current?.support ?? initialSupportSelection);
|
|
const [voip, setVoIP] = useState<VoIPSelection>(draft.current?.voip ?? initialVoIPSelection);
|
|
const [webHosting, setWebHosting] = useState<WebHostingSelection>(draft.current?.webHosting ?? initialWebHostingSelection);
|
|
const [email, setEmail] = useState<EmailSelection>(draft.current?.email ?? initialEmailSelection);
|
|
const [contact, setContact] = useState<ContactInfo>(draft.current?.contact ?? initialContactInfo);
|
|
const [quoteResult, setQuoteResult] = useState<QuoteResult | null>(null);
|
|
|
|
// Persist draft to localStorage when any section changes (debounced)
|
|
const saveTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
useEffect(() => {
|
|
if (saveTimerRef.current) {
|
|
clearTimeout(saveTimerRef.current);
|
|
}
|
|
saveTimerRef.current = setTimeout(() => {
|
|
try {
|
|
// Preserve the accessToken that WizardContainer may have written
|
|
const existing = localStorage.getItem(DRAFT_STORAGE_KEY);
|
|
let accessToken: string | undefined;
|
|
if (existing) {
|
|
try {
|
|
accessToken = JSON.parse(existing).accessToken;
|
|
} catch {
|
|
// ignore
|
|
}
|
|
}
|
|
const payload = {
|
|
clientType,
|
|
serviceInterests,
|
|
company,
|
|
gps,
|
|
support,
|
|
voip,
|
|
webHosting,
|
|
email,
|
|
contact,
|
|
...(accessToken ? { accessToken } : {}),
|
|
};
|
|
localStorage.setItem(DRAFT_STORAGE_KEY, JSON.stringify(payload));
|
|
} catch {
|
|
// localStorage write failures are non-critical
|
|
}
|
|
}, 500);
|
|
|
|
return () => {
|
|
if (saveTimerRef.current) {
|
|
clearTimeout(saveTimerRef.current);
|
|
}
|
|
};
|
|
}, [clientType, serviceInterests, company, gps, support, voip, webHosting, email, contact]);
|
|
|
|
// Combined quote data
|
|
const quoteData: QuoteData = useMemo(
|
|
() => ({
|
|
clientType,
|
|
serviceInterests,
|
|
company,
|
|
gps,
|
|
support,
|
|
voip,
|
|
webHosting,
|
|
email,
|
|
contact,
|
|
}),
|
|
[clientType, serviceInterests, company, gps, support, voip, webHosting, email, contact]
|
|
);
|
|
|
|
// ============================================================================
|
|
// Client Type & Service Interests
|
|
// ============================================================================
|
|
|
|
const setClientTypeValue = useCallback((type: ClientType) => {
|
|
setClientType(type);
|
|
}, []);
|
|
|
|
const setServiceInterest = useCallback((service: keyof ServiceInterests, enabled: boolean) => {
|
|
setServiceInterests((prev) => ({ ...prev, [service]: enabled }));
|
|
// Sync the enabled flags on the corresponding selections
|
|
if (service === 'voip') {
|
|
setVoIP((prev) => ({ ...prev, enabled, userCount: enabled ? Math.max(prev.userCount, 1) : 0 }));
|
|
}
|
|
if (service === 'webHosting') {
|
|
setWebHosting((prev) => ({ ...prev, enabled }));
|
|
}
|
|
if (service === 'email') {
|
|
setEmail((prev) => ({ ...prev, enabled, mailboxCount: enabled ? Math.max(prev.mailboxCount, 1) : 0 }));
|
|
}
|
|
}, []);
|
|
|
|
// ============================================================================
|
|
// Company Updates
|
|
// ============================================================================
|
|
|
|
const updateCompany = useCallback((data: Partial<CompanyInfo>) => {
|
|
setCompany((prev) => {
|
|
const updated = { ...prev, ...data };
|
|
// Sync endpoint count with GPS selection
|
|
if (data.endpointCount !== undefined) {
|
|
setGPS((gpsState) => ({ ...gpsState, endpointCount: data.endpointCount as number }));
|
|
}
|
|
return updated;
|
|
});
|
|
}, []);
|
|
|
|
const setEndpointCount = useCallback((count: number) => {
|
|
const validCount = Math.max(1, count);
|
|
setCompany((prev) => ({ ...prev, endpointCount: validCount }));
|
|
setGPS((prev) => ({ ...prev, endpointCount: validCount }));
|
|
}, []);
|
|
|
|
const setIndustry = useCallback((industry: Industry | '') => {
|
|
setCompany((prev) => ({ ...prev, industry }));
|
|
}, []);
|
|
|
|
// ============================================================================
|
|
// GPS Updates
|
|
// ============================================================================
|
|
|
|
const updateGPS = useCallback((data: Partial<GPSSelection>) => {
|
|
setGPS((prev) => ({ ...prev, ...data }));
|
|
}, []);
|
|
|
|
const setGPSTier = useCallback((tierId: GPSTierId) => {
|
|
setGPS((prev) => ({ ...prev, tierId }));
|
|
}, []);
|
|
|
|
const setEquipmentEnabled = useCallback((enabled: boolean) => {
|
|
setGPS((prev) => ({
|
|
...prev,
|
|
includeEquipment: enabled,
|
|
equipmentDeviceCount: enabled ? Math.max(prev.equipmentDeviceCount, 1) : 0,
|
|
}));
|
|
}, []);
|
|
|
|
const setEquipmentCount = useCallback((count: number) => {
|
|
setGPS((prev) => ({ ...prev, equipmentDeviceCount: Math.max(0, count) }));
|
|
}, []);
|
|
|
|
// ============================================================================
|
|
// Support Updates
|
|
// ============================================================================
|
|
|
|
const updateSupport = useCallback((data: Partial<SupportSelection>) => {
|
|
setSupport((prev) => ({ ...prev, ...data }));
|
|
}, []);
|
|
|
|
const setSupportPlan = useCallback((planId: SupportPlanId) => {
|
|
setSupport((prev) => ({ ...prev, planId }));
|
|
}, []);
|
|
|
|
const setBlockTimeEnabled = useCallback((enabled: boolean) => {
|
|
setSupport((prev) => ({
|
|
...prev,
|
|
useBlockTime: enabled,
|
|
blockTimeId: enabled ? (prev.blockTimeId || 'block-10') : null,
|
|
}));
|
|
}, []);
|
|
|
|
const setBlockTime = useCallback((blockTimeId: BlockTimeId) => {
|
|
setSupport((prev) => ({ ...prev, blockTimeId, useBlockTime: true }));
|
|
}, []);
|
|
|
|
// ============================================================================
|
|
// VoIP Updates
|
|
// ============================================================================
|
|
|
|
const updateVoIP = useCallback((data: Partial<VoIPSelection>) => {
|
|
setVoIP((prev) => ({ ...prev, ...data }));
|
|
}, []);
|
|
|
|
const setVoIPEnabled = useCallback((enabled: boolean) => {
|
|
setVoIP((prev) => ({
|
|
...prev,
|
|
enabled,
|
|
userCount: enabled ? Math.max(prev.userCount, 1) : 0,
|
|
}));
|
|
}, []);
|
|
|
|
const setVoIPTier = useCallback((tierId: VoIPTierId) => {
|
|
setVoIP((prev) => ({ ...prev, tierId }));
|
|
}, []);
|
|
|
|
const setVoIPUserCount = useCallback((count: number) => {
|
|
setVoIP((prev) => ({ ...prev, userCount: Math.max(0, count) }));
|
|
}, []);
|
|
|
|
const addHardware = useCallback((hardwareId: string, quantity: number, isRental: boolean) => {
|
|
setVoIP((prev) => {
|
|
const existing = prev.hardware.find((h) => h.hardwareId === hardwareId);
|
|
if (existing) {
|
|
return {
|
|
...prev,
|
|
hardware: prev.hardware.map((h) =>
|
|
h.hardwareId === hardwareId ? { ...h, quantity, isRental } : h
|
|
),
|
|
};
|
|
}
|
|
return {
|
|
...prev,
|
|
hardware: [...prev.hardware, { hardwareId, quantity, isRental }],
|
|
};
|
|
});
|
|
}, []);
|
|
|
|
const removeHardware = useCallback((hardwareId: string) => {
|
|
setVoIP((prev) => ({
|
|
...prev,
|
|
hardware: prev.hardware.filter((h) => h.hardwareId !== hardwareId),
|
|
}));
|
|
}, []);
|
|
|
|
const updateHardwareQuantity = useCallback((hardwareId: string, quantity: number) => {
|
|
setVoIP((prev) => ({
|
|
...prev,
|
|
hardware: prev.hardware.map((h) =>
|
|
h.hardwareId === hardwareId ? { ...h, quantity: Math.max(0, quantity) } : h
|
|
),
|
|
}));
|
|
}, []);
|
|
|
|
// ============================================================================
|
|
// Web Hosting Updates
|
|
// ============================================================================
|
|
|
|
const updateWebHosting = useCallback((data: Partial<WebHostingSelection>) => {
|
|
setWebHosting((prev) => ({ ...prev, ...data }));
|
|
}, []);
|
|
|
|
const setWebHostingEnabled = useCallback((enabled: boolean) => {
|
|
setWebHosting((prev) => ({ ...prev, enabled }));
|
|
}, []);
|
|
|
|
const setWebHostingTier = useCallback((tierId: WebHostingTierId) => {
|
|
setWebHosting((prev) => ({ ...prev, tierId }));
|
|
}, []);
|
|
|
|
// ============================================================================
|
|
// Email Updates
|
|
// ============================================================================
|
|
|
|
const updateEmail = useCallback((data: Partial<EmailSelection>) => {
|
|
setEmail((prev) => ({ ...prev, ...data }));
|
|
}, []);
|
|
|
|
const setEmailEnabled = useCallback((enabled: boolean) => {
|
|
setEmail((prev) => ({
|
|
...prev,
|
|
enabled,
|
|
mailboxCount: enabled ? Math.max(prev.mailboxCount, 1) : 0,
|
|
}));
|
|
}, []);
|
|
|
|
const setEmailProvider = useCallback((provider: EmailProvider) => {
|
|
setEmail((prev) => {
|
|
// Set default tier for provider
|
|
const defaultTier = provider === 'm365' ? 'm365-standard' : 'whm-standard';
|
|
return { ...prev, provider, tierId: defaultTier as EmailTierId };
|
|
});
|
|
}, []);
|
|
|
|
const setEmailTier = useCallback((tierId: EmailTierId) => {
|
|
setEmail((prev) => ({ ...prev, tierId }));
|
|
}, []);
|
|
|
|
const setMailboxCount = useCallback((count: number) => {
|
|
setEmail((prev) => ({ ...prev, mailboxCount: Math.max(0, count) }));
|
|
}, []);
|
|
|
|
// ============================================================================
|
|
// Contact Updates
|
|
// ============================================================================
|
|
|
|
const updateContact = useCallback((data: Partial<ContactInfo>) => {
|
|
setContact((prev) => ({ ...prev, ...data }));
|
|
}, []);
|
|
|
|
const setContactPreference = useCallback((preference: ContactPreference) => {
|
|
setContact((prev) => ({ ...prev, contactPreference: preference }));
|
|
}, []);
|
|
|
|
const setAgreedToTerms = useCallback((agreed: boolean) => {
|
|
setContact((prev) => ({ ...prev, agreedToTerms: agreed }));
|
|
}, []);
|
|
|
|
// ============================================================================
|
|
// Calculation Functions
|
|
// ============================================================================
|
|
|
|
const getGPSMonthly = useCallback((): number => {
|
|
const tier = gpsTiers.find((t) => t.id === gps.tierId);
|
|
if (!tier) return 0;
|
|
|
|
let total = tier.pricePerEndpoint * gps.endpointCount;
|
|
|
|
if (gps.includeEquipment && gps.equipmentDeviceCount > 0) {
|
|
const additionalDevices = Math.max(0, gps.equipmentDeviceCount - equipmentMonitoring.baseDevices);
|
|
total += equipmentMonitoring.basePrice + (additionalDevices * equipmentMonitoring.additionalDevicePrice);
|
|
}
|
|
|
|
return total;
|
|
}, [gps]);
|
|
|
|
const getSupportMonthly = useCallback((): number => {
|
|
if (support.planId === 'none') return 0;
|
|
|
|
const plan = supportPlans.find((p) => p.id === support.planId);
|
|
return plan ? plan.monthlyPrice : 0;
|
|
}, [support]);
|
|
|
|
const getSupportBlockTimeOneTime = useCallback((): number => {
|
|
if (!support.useBlockTime || !support.blockTimeId) return 0;
|
|
const blockTime = blockTimeOptions.find((b) => b.id === support.blockTimeId);
|
|
return blockTime ? blockTime.price : 0;
|
|
}, [support]);
|
|
|
|
const getVoIPMonthly = useCallback((): number => {
|
|
if (!voip.enabled) return 0;
|
|
|
|
const tier = voipTiers.find((t) => t.id === voip.tierId);
|
|
if (!tier) return 0;
|
|
|
|
let total = tier.pricePerUser * voip.userCount;
|
|
|
|
// Add rental hardware costs
|
|
voip.hardware.forEach((hw) => {
|
|
if (hw.isRental) {
|
|
const hardware = voipHardware.find((h) => h.id === hw.hardwareId);
|
|
if (hardware) {
|
|
total += hardware.monthlyRental * hw.quantity;
|
|
}
|
|
}
|
|
});
|
|
|
|
return total;
|
|
}, [voip]);
|
|
|
|
const getVoIPOneTime = useCallback((): number => {
|
|
if (!voip.enabled) return 0;
|
|
|
|
let total = 0;
|
|
|
|
// Add purchased hardware costs
|
|
voip.hardware.forEach((hw) => {
|
|
if (!hw.isRental) {
|
|
const hardware = voipHardware.find((h) => h.id === hw.hardwareId);
|
|
if (hardware) {
|
|
total += hardware.oneTimePrice * hw.quantity;
|
|
}
|
|
}
|
|
});
|
|
|
|
return total;
|
|
}, [voip]);
|
|
|
|
const getWebHostingMonthly = useCallback((): number => {
|
|
if (!webHosting.enabled) return 0;
|
|
|
|
const tier = webHostingTiers.find((t) => t.id === webHosting.tierId);
|
|
return tier ? tier.monthlyPrice : 0;
|
|
}, [webHosting]);
|
|
|
|
const getEmailMonthly = useCallback((): number => {
|
|
if (!email.enabled) return 0;
|
|
|
|
const tier = emailTiers.find((t) => t.id === email.tierId);
|
|
return tier ? tier.pricePerMailbox * email.mailboxCount : 0;
|
|
}, [email]);
|
|
|
|
const calculateQuote = useCallback((): QuoteResult => {
|
|
// Only include services that are enabled in serviceInterests
|
|
const gpsMonthly = serviceInterests.gps ? getGPSMonthly() : 0;
|
|
const supportMonthly = serviceInterests.support ? getSupportMonthly() : 0;
|
|
const supportBlockTimeOneTime = serviceInterests.support ? getSupportBlockTimeOneTime() : 0;
|
|
const voipMonthly = serviceInterests.voip ? getVoIPMonthly() : 0;
|
|
const voipOneTime = serviceInterests.voip ? getVoIPOneTime() : 0;
|
|
const webHostingMonthly = serviceInterests.webHosting ? getWebHostingMonthly() : 0;
|
|
const emailMonthly = serviceInterests.email ? getEmailMonthly() : 0;
|
|
|
|
// Calculate GPS breakdown (only if enabled)
|
|
let gpsMonitoring = 0;
|
|
let gpsEquipment = 0;
|
|
if (serviceInterests.gps) {
|
|
const gpsTier = gpsTiers.find((t) => t.id === gps.tierId);
|
|
gpsMonitoring = gpsTier ? gpsTier.pricePerEndpoint * gps.endpointCount : 0;
|
|
if (gps.includeEquipment && gps.equipmentDeviceCount > 0) {
|
|
const additionalDevices = Math.max(0, gps.equipmentDeviceCount - equipmentMonitoring.baseDevices);
|
|
gpsEquipment = equipmentMonitoring.basePrice + (additionalDevices * equipmentMonitoring.additionalDevicePrice);
|
|
}
|
|
}
|
|
|
|
// Calculate support breakdown (only if enabled)
|
|
let supportPlanCost = 0;
|
|
if (serviceInterests.support && support.planId !== 'none') {
|
|
const supportPlan = supportPlans.find((p) => p.id === support.planId);
|
|
supportPlanCost = supportPlan ? supportPlan.monthlyPrice : 0;
|
|
}
|
|
|
|
// Calculate VoIP breakdown (only if enabled)
|
|
let voipService = 0;
|
|
let voipHardwareMonthly = 0;
|
|
if (serviceInterests.voip && voip.enabled) {
|
|
const voipTier = voipTiers.find((t) => t.id === voip.tierId);
|
|
voipService = voipTier ? voipTier.pricePerUser * voip.userCount : 0;
|
|
voip.hardware.forEach((hw) => {
|
|
if (hw.isRental) {
|
|
const hardware = voipHardware.find((h) => h.id === hw.hardwareId);
|
|
if (hardware) {
|
|
voipHardwareMonthly += hardware.monthlyRental * hw.quantity;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
const breakdown: QuoteBreakdown = {
|
|
gps: {
|
|
monitoring: gpsMonitoring,
|
|
equipment: gpsEquipment,
|
|
total: gpsMonthly,
|
|
},
|
|
support: {
|
|
plan: supportPlanCost,
|
|
blockTime: supportBlockTimeOneTime,
|
|
total: supportMonthly,
|
|
},
|
|
voip: {
|
|
service: voipService,
|
|
hardware: voipHardwareMonthly,
|
|
total: voipMonthly,
|
|
},
|
|
webHosting: webHostingMonthly,
|
|
email: emailMonthly,
|
|
};
|
|
|
|
const monthlyTotal = gpsMonthly + supportMonthly + voipMonthly + webHostingMonthly + emailMonthly;
|
|
|
|
const result: QuoteResult = {
|
|
monthlyTotal,
|
|
oneTimeTotal: voipOneTime + supportBlockTimeOneTime,
|
|
breakdown,
|
|
gpsMonthly,
|
|
supportMonthly,
|
|
voipMonthly,
|
|
webHostingMonthly,
|
|
emailMonthly,
|
|
};
|
|
|
|
setQuoteResult(result);
|
|
return result;
|
|
}, [serviceInterests, gps, support, voip, webHosting, email, getGPSMonthly, getSupportMonthly, getSupportBlockTimeOneTime, getVoIPMonthly, getVoIPOneTime, getWebHostingMonthly, getEmailMonthly]);
|
|
|
|
// ============================================================================
|
|
// Reset
|
|
// ============================================================================
|
|
|
|
const resetQuote = useCallback(() => {
|
|
setClientType(initialClientType);
|
|
setServiceInterests(initialServiceInterests);
|
|
setCompany(initialCompanyInfo);
|
|
setGPS(initialGPSSelection);
|
|
setSupport(initialSupportSelection);
|
|
setVoIP(initialVoIPSelection);
|
|
setWebHosting(initialWebHostingSelection);
|
|
setEmail(initialEmailSelection);
|
|
setContact(initialContactInfo);
|
|
setQuoteResult(null);
|
|
localStorage.removeItem(DRAFT_STORAGE_KEY);
|
|
}, []);
|
|
|
|
return {
|
|
quoteData,
|
|
quoteResult,
|
|
|
|
// Client type & service interests
|
|
setClientType: setClientTypeValue,
|
|
setServiceInterest,
|
|
|
|
// Company updates
|
|
updateCompany,
|
|
setEndpointCount,
|
|
setIndustry,
|
|
|
|
// GPS updates
|
|
updateGPS,
|
|
setGPSTier,
|
|
setEquipmentEnabled,
|
|
setEquipmentCount,
|
|
|
|
// Support updates
|
|
updateSupport,
|
|
setSupportPlan,
|
|
setBlockTimeEnabled,
|
|
setBlockTime,
|
|
|
|
// VoIP updates
|
|
updateVoIP,
|
|
setVoIPEnabled,
|
|
setVoIPTier,
|
|
setVoIPUserCount,
|
|
addHardware,
|
|
removeHardware,
|
|
updateHardwareQuantity,
|
|
|
|
// Web Hosting updates
|
|
updateWebHosting,
|
|
setWebHostingEnabled,
|
|
setWebHostingTier,
|
|
|
|
// Email updates
|
|
updateEmail,
|
|
setEmailEnabled,
|
|
setEmailProvider,
|
|
setEmailTier,
|
|
setMailboxCount,
|
|
|
|
// Contact updates
|
|
updateContact,
|
|
setContactPreference,
|
|
setAgreedToTerms,
|
|
|
|
// Calculations
|
|
calculateQuote,
|
|
getGPSMonthly,
|
|
getSupportMonthly,
|
|
getVoIPMonthly,
|
|
getWebHostingMonthly,
|
|
getEmailMonthly,
|
|
getSupportBlockTimeOneTime,
|
|
getVoIPOneTime,
|
|
|
|
// Reset
|
|
resetQuote,
|
|
};
|
|
}
|