fix: Quote wizard - correct total calculation and email sender
- 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>
This commit is contained in:
@@ -88,54 +88,58 @@ export function Step6Summary({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* GPS Monitoring Section */}
|
{/* GPS Monitoring Section - only show if enabled */}
|
||||||
<SummarySection
|
{quoteData.serviceInterests.gps && (
|
||||||
icon={<Monitor className="w-5 h-5" />}
|
<SummarySection
|
||||||
title="GPS Monitoring"
|
icon={<Monitor className="w-5 h-5" />}
|
||||||
monthlyTotal={result.gpsMonthly}
|
title="GPS Monitoring"
|
||||||
onEdit={() => onGoToStep(1)}
|
monthlyTotal={result.gpsMonthly}
|
||||||
>
|
onEdit={() => onGoToStep(1)}
|
||||||
<div className="space-y-2">
|
>
|
||||||
<SummaryLine
|
<div className="space-y-2">
|
||||||
label={`${gpsTier?.name} Plan (${quoteData.gps.endpointCount} endpoints)`}
|
|
||||||
value={formatCurrency(result.breakdown.gps.monitoring)}
|
|
||||||
/>
|
|
||||||
{quoteData.gps.includeEquipment && quoteData.gps.equipmentDeviceCount > 0 && (
|
|
||||||
<SummaryLine
|
<SummaryLine
|
||||||
label={`Equipment Pack (${quoteData.gps.equipmentDeviceCount} devices)`}
|
label={`${gpsTier?.name} Plan (${quoteData.gps.endpointCount} endpoints)`}
|
||||||
value={formatCurrency(result.breakdown.gps.equipment)}
|
value={formatCurrency(result.breakdown.gps.monitoring)}
|
||||||
/>
|
/>
|
||||||
)}
|
{quoteData.gps.includeEquipment && quoteData.gps.equipmentDeviceCount > 0 && (
|
||||||
</div>
|
<SummaryLine
|
||||||
</SummarySection>
|
label={`Equipment Pack (${quoteData.gps.equipmentDeviceCount} devices)`}
|
||||||
|
value={formatCurrency(result.breakdown.gps.equipment)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</SummarySection>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Support Plan Section */}
|
{/* Support Plan Section - only show if enabled */}
|
||||||
<SummarySection
|
{quoteData.serviceInterests.support && (
|
||||||
icon={<Headphones className="w-5 h-5" />}
|
<SummarySection
|
||||||
title="Support Plan"
|
icon={<Headphones className="w-5 h-5" />}
|
||||||
monthlyTotal={result.supportMonthly}
|
title="Support Plan"
|
||||||
onEdit={() => onGoToStep(2)}
|
monthlyTotal={result.supportMonthly}
|
||||||
>
|
onEdit={() => onGoToStep(2)}
|
||||||
<div className="space-y-2">
|
>
|
||||||
{quoteData.support.planId === 'none' ? (
|
<div className="space-y-2">
|
||||||
<SummaryLine
|
{quoteData.support.planId === 'none' ? (
|
||||||
label="No Monthly Plan (pay-as-you-go)"
|
<SummaryLine
|
||||||
value="$0"
|
label="No Monthly Plan (pay-as-you-go)"
|
||||||
/>
|
value="$0"
|
||||||
) : (
|
/>
|
||||||
<SummaryLine
|
) : (
|
||||||
label={`${supportPlan?.name} Plan (${supportPlan?.includedHours} hrs/mo)`}
|
<SummaryLine
|
||||||
value={formatCurrency(result.breakdown.support.plan)}
|
label={`${supportPlan?.name} Plan (${supportPlan?.includedHours} hrs/mo)`}
|
||||||
/>
|
value={formatCurrency(result.breakdown.support.plan)}
|
||||||
)}
|
/>
|
||||||
{blockTime && (
|
)}
|
||||||
<SummaryLine
|
{blockTime && (
|
||||||
label={`Block Time (${blockTime.hours} hours) — one-time`}
|
<SummaryLine
|
||||||
value={formatCurrency(result.breakdown.support.blockTime)}
|
label={`Block Time (${blockTime.hours} hours) — one-time`}
|
||||||
/>
|
value={formatCurrency(result.breakdown.support.blockTime)}
|
||||||
)}
|
/>
|
||||||
</div>
|
)}
|
||||||
</SummarySection>
|
</div>
|
||||||
|
</SummarySection>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* VoIP Section */}
|
{/* VoIP Section */}
|
||||||
{quoteData.voip.enabled && (
|
{quoteData.voip.enabled && (
|
||||||
@@ -230,15 +234,19 @@ export function Step6Summary({
|
|||||||
Monthly Breakdown
|
Monthly Breakdown
|
||||||
</h4>
|
</h4>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<BreakdownRow label="GPS Monitoring" value={result.gpsMonthly} />
|
{quoteData.serviceInterests.gps && (
|
||||||
<BreakdownRow label="Support Plan" value={result.supportMonthly} />
|
<BreakdownRow label="GPS Monitoring" value={result.gpsMonthly} />
|
||||||
{quoteData.voip.enabled && (
|
)}
|
||||||
|
{quoteData.serviceInterests.support && (
|
||||||
|
<BreakdownRow label="Support Plan" value={result.supportMonthly} />
|
||||||
|
)}
|
||||||
|
{quoteData.serviceInterests.voip && quoteData.voip.enabled && (
|
||||||
<BreakdownRow label="VoIP Phone System" value={result.voipMonthly} />
|
<BreakdownRow label="VoIP Phone System" value={result.voipMonthly} />
|
||||||
)}
|
)}
|
||||||
{quoteData.webHosting.enabled && (
|
{quoteData.serviceInterests.webHosting && quoteData.webHosting.enabled && (
|
||||||
<BreakdownRow label="Web Hosting" value={result.webHostingMonthly} />
|
<BreakdownRow label="Web Hosting" value={result.webHostingMonthly} />
|
||||||
)}
|
)}
|
||||||
{quoteData.email.enabled && (
|
{quoteData.serviceInterests.email && quoteData.email.enabled && (
|
||||||
<BreakdownRow label="Email Service" value={result.emailMonthly} />
|
<BreakdownRow label="Email Service" value={result.emailMonthly} />
|
||||||
)}
|
)}
|
||||||
<div className="pt-4 mt-1 border-t-2 border-[#fe7400]/20 flex justify-between items-center">
|
<div className="pt-4 mt-1 border-t-2 border-[#fe7400]/20 flex justify-between items-center">
|
||||||
|
|||||||
@@ -568,32 +568,40 @@ export function useQuote(): UseQuoteReturn {
|
|||||||
}, [email]);
|
}, [email]);
|
||||||
|
|
||||||
const calculateQuote = useCallback((): QuoteResult => {
|
const calculateQuote = useCallback((): QuoteResult => {
|
||||||
const gpsMonthly = getGPSMonthly();
|
// Only include services that are enabled in serviceInterests
|
||||||
const supportMonthly = getSupportMonthly();
|
const gpsMonthly = serviceInterests.gps ? getGPSMonthly() : 0;
|
||||||
const voipMonthly = getVoIPMonthly();
|
const supportMonthly = serviceInterests.support ? getSupportMonthly() : 0;
|
||||||
const voipOneTime = getVoIPOneTime();
|
const supportBlockTimeOneTime = serviceInterests.support ? getSupportBlockTimeOneTime() : 0;
|
||||||
const supportBlockTimeOneTime = getSupportBlockTimeOneTime();
|
const voipMonthly = serviceInterests.voip ? getVoIPMonthly() : 0;
|
||||||
const webHostingMonthly = getWebHostingMonthly();
|
const voipOneTime = serviceInterests.voip ? getVoIPOneTime() : 0;
|
||||||
const emailMonthly = getEmailMonthly();
|
const webHostingMonthly = serviceInterests.webHosting ? getWebHostingMonthly() : 0;
|
||||||
|
const emailMonthly = serviceInterests.email ? getEmailMonthly() : 0;
|
||||||
|
|
||||||
// Calculate GPS breakdown
|
// Calculate GPS breakdown (only if enabled)
|
||||||
const gpsTier = gpsTiers.find((t) => t.id === gps.tierId);
|
let gpsMonitoring = 0;
|
||||||
const gpsMonitoring = gpsTier ? gpsTier.pricePerEndpoint * gps.endpointCount : 0;
|
|
||||||
let gpsEquipment = 0;
|
let gpsEquipment = 0;
|
||||||
if (gps.includeEquipment && gps.equipmentDeviceCount > 0) {
|
if (serviceInterests.gps) {
|
||||||
const additionalDevices = Math.max(0, gps.equipmentDeviceCount - equipmentMonitoring.baseDevices);
|
const gpsTier = gpsTiers.find((t) => t.id === gps.tierId);
|
||||||
gpsEquipment = equipmentMonitoring.basePrice + (additionalDevices * equipmentMonitoring.additionalDevicePrice);
|
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
|
// Calculate support breakdown (only if enabled)
|
||||||
const supportPlan = support.planId !== 'none' ? supportPlans.find((p) => p.id === support.planId) : null;
|
let supportPlanCost = 0;
|
||||||
const supportPlanCost = supportPlan ? supportPlan.monthlyPrice : 0;
|
if (serviceInterests.support && support.planId !== 'none') {
|
||||||
|
const supportPlan = supportPlans.find((p) => p.id === support.planId);
|
||||||
|
supportPlanCost = supportPlan ? supportPlan.monthlyPrice : 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate VoIP breakdown
|
// Calculate VoIP breakdown (only if enabled)
|
||||||
const voipTier = voipTiers.find((t) => t.id === voip.tierId);
|
let voipService = 0;
|
||||||
const voipService = voip.enabled && voipTier ? voipTier.pricePerUser * voip.userCount : 0;
|
|
||||||
let voipHardwareMonthly = 0;
|
let voipHardwareMonthly = 0;
|
||||||
if (voip.enabled) {
|
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) => {
|
voip.hardware.forEach((hw) => {
|
||||||
if (hw.isRental) {
|
if (hw.isRental) {
|
||||||
const hardware = voipHardware.find((h) => h.id === hw.hardwareId);
|
const hardware = voipHardware.find((h) => h.id === hw.hardwareId);
|
||||||
@@ -639,7 +647,7 @@ export function useQuote(): UseQuoteReturn {
|
|||||||
|
|
||||||
setQuoteResult(result);
|
setQuoteResult(result);
|
||||||
return result;
|
return result;
|
||||||
}, [gps, support, voip, webHosting, email, getGPSMonthly, getSupportMonthly, getSupportBlockTimeOneTime, getVoIPMonthly, getVoIPOneTime, getWebHostingMonthly, getEmailMonthly]);
|
}, [serviceInterests, gps, support, voip, webHosting, email, getGPSMonthly, getSupportMonthly, getSupportBlockTimeOneTime, getVoIPMonthly, getVoIPOneTime, getWebHostingMonthly, getEmailMonthly]);
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Reset
|
// Reset
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ define('GRAPH_TENANT_ID', 'ce61461e-81a0-4c84-bb4a-7b354a9a356d');
|
|||||||
define('GRAPH_CLIENT_ID', '15b0fafb-ab51-4cc9-adc7-f6334c805c22');
|
define('GRAPH_CLIENT_ID', '15b0fafb-ab51-4cc9-adc7-f6334c805c22');
|
||||||
define('GRAPH_CLIENT_SECRET', 'rRN8Q~FPfSL8O24iZthi_LVJTjGOCZG.DnxGHaSk');
|
define('GRAPH_CLIENT_SECRET', 'rRN8Q~FPfSL8O24iZthi_LVJTjGOCZG.DnxGHaSk');
|
||||||
define('GRAPH_SENDER_EMAIL', 'noreply@azcomputerguru.com');
|
define('GRAPH_SENDER_EMAIL', 'noreply@azcomputerguru.com');
|
||||||
|
define('GRAPH_SENDER_NAME', 'Arizona Computer Guru');
|
||||||
|
define('GRAPH_REPLY_TO_EMAIL', 'admin@azcomputerguru.com');
|
||||||
|
define('GRAPH_REPLY_TO_NAME', 'Arizona Computer Guru');
|
||||||
|
|
||||||
// --------------------------------------------------------------------------
|
// --------------------------------------------------------------------------
|
||||||
// Admin / Auth
|
// Admin / Auth
|
||||||
|
|||||||
@@ -124,9 +124,23 @@ function send_email(string $to_email, string $subject, string $body_html, ?strin
|
|||||||
'contentType' => 'HTML',
|
'contentType' => 'HTML',
|
||||||
'content' => $body_html,
|
'content' => $body_html,
|
||||||
],
|
],
|
||||||
|
'from' => [
|
||||||
|
'emailAddress' => [
|
||||||
|
'name' => defined('GRAPH_SENDER_NAME') ? GRAPH_SENDER_NAME : 'Arizona Computer Guru',
|
||||||
|
'address' => GRAPH_SENDER_EMAIL,
|
||||||
|
],
|
||||||
|
],
|
||||||
'toRecipients' => [
|
'toRecipients' => [
|
||||||
['emailAddress' => ['address' => $to_email]],
|
['emailAddress' => ['address' => $to_email]],
|
||||||
],
|
],
|
||||||
|
'replyTo' => [
|
||||||
|
[
|
||||||
|
'emailAddress' => [
|
||||||
|
'name' => defined('GRAPH_REPLY_TO_NAME') ? GRAPH_REPLY_TO_NAME : 'Arizona Computer Guru',
|
||||||
|
'address' => defined('GRAPH_REPLY_TO_EMAIL') ? GRAPH_REPLY_TO_EMAIL : GRAPH_SENDER_EMAIL,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
],
|
],
|
||||||
'saveToSentItems' => 'true',
|
'saveToSentItems' => 'true',
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user