diff --git a/apps/bff/src/orders/dto/order.dto.ts b/apps/bff/src/orders/dto/order.dto.ts
index cb48327b..6ff25775 100644
--- a/apps/bff/src/orders/dto/order.dto.ts
+++ b/apps/bff/src/orders/dto/order.dto.ts
@@ -78,6 +78,18 @@ export class OrderConfigurations {
@IsString()
portingDateOfBirth?: string;
+ // Address (when address is updated during checkout)
+ @IsOptional()
+ @IsObject()
+ address?: {
+ street?: string | null;
+ streetLine2?: string | null;
+ city?: string | null;
+ state?: string | null;
+ postalCode?: string | null;
+ country?: string | null;
+ };
+
// VPN region is inferred from product VPN_Region__c field, no user input needed
}
diff --git a/apps/bff/src/orders/orders.controller.ts b/apps/bff/src/orders/orders.controller.ts
index 648db600..cf4647d2 100644
--- a/apps/bff/src/orders/orders.controller.ts
+++ b/apps/bff/src/orders/orders.controller.ts
@@ -24,6 +24,7 @@ export class OrdersController {
userId: req.user?.id,
orderType: body.orderType,
skuCount: body.skus?.length || 0,
+ requestBody: JSON.stringify(body, null, 2),
},
"Order creation request received"
);
@@ -34,8 +35,10 @@ export class OrdersController {
this.logger.error(
{
error: error instanceof Error ? error.message : String(error),
+ stack: error instanceof Error ? error.stack : undefined,
userId: req.user?.id,
orderType: body.orderType,
+ fullRequestBody: JSON.stringify(body, null, 2),
},
"Order creation failed"
);
diff --git a/apps/bff/src/orders/services/order-orchestrator.service.ts b/apps/bff/src/orders/services/order-orchestrator.service.ts
index 58a9b46c..3f6b8bfd 100644
--- a/apps/bff/src/orders/services/order-orchestrator.service.ts
+++ b/apps/bff/src/orders/services/order-orchestrator.service.ts
@@ -57,13 +57,24 @@ export class OrderOrchestrator {
// 4) Create Order in Salesforce
let created: { id: string };
try {
+ this.logger.log(
+ {
+ orderFields: JSON.stringify(orderFields, null, 2),
+ fieldsCount: Object.keys(orderFields).length
+ },
+ "About to create Salesforce Order with fields"
+ );
+
created = (await this.sf.sobject("Order").create(orderFields)) as { id: string };
this.logger.log({ orderId: created.id }, "Salesforce Order created successfully");
} catch (error) {
this.logger.error(
{
error: error instanceof Error ? error.message : String(error),
+ errorDetails: error,
+ stack: error instanceof Error ? error.stack : undefined,
orderType: orderFields.Type,
+ orderFields: JSON.stringify(orderFields, null, 2),
},
"Failed to create Salesforce Order"
);
diff --git a/apps/portal/src/app/checkout/page.tsx b/apps/portal/src/app/checkout/page.tsx
index f19f8b42..745c5426 100644
--- a/apps/portal/src/app/checkout/page.tsx
+++ b/apps/portal/src/app/checkout/page.tsx
@@ -1,6 +1,6 @@
"use client";
-import { useState, useEffect, useMemo, Suspense } from "react";
+import { useState, useEffect, useMemo, useCallback, Suspense } from "react";
import { useSearchParams, useRouter } from "next/navigation";
import { PageLayout } from "@/components/layout/page-layout";
import { ShieldCheckIcon, ExclamationTriangleIcon } from "@heroicons/react/24/outline";
@@ -38,6 +38,7 @@ function CheckoutContent() {
const [submitting, setSubmitting] = useState(false);
const [addressConfirmed, setAddressConfirmed] = useState(false);
const [confirmedAddress, setConfirmedAddress] = useState
(null);
+ const [forceUpdate, setForceUpdate] = useState(0);
const [checkoutState, setCheckoutState] = useState({
loading: true,
error: null,
@@ -169,6 +170,11 @@ function CheckoutContent() {
};
}, [orderType, selections]);
+ // Debug effect to track addressConfirmed changes
+ useEffect(() => {
+ console.log("🎯 PARENT: addressConfirmed state changed to:", addressConfirmed);
+ }, [addressConfirmed]);
+
const handleSubmitOrder = async () => {
try {
setSubmitting(true);
@@ -180,6 +186,28 @@ function CheckoutContent() {
throw new Error("No products selected for order. Please go back and select products.");
}
+ // Additional validation for Internet orders
+ if (orderType === "Internet") {
+ const hasServicePlan = checkoutState.orderItems.some(item => item.type === "service");
+ const hasInstallation = checkoutState.orderItems.some(item => item.type === "installation");
+
+ console.log("🔍 Internet order validation:", {
+ hasServicePlan,
+ hasInstallation,
+ orderItems: checkoutState.orderItems,
+ selections: selections
+ });
+
+ if (!hasServicePlan) {
+ throw new Error("Internet service plan is required. Please go back and select a plan.");
+ }
+
+ // Installation is typically required for Internet orders
+ if (!hasInstallation) {
+ console.warn("⚠️ No installation selected for Internet order - this might cause issues");
+ }
+ }
+
// Send SKUs + configurations - backend resolves product data from SKUs,
// uses configurations for fields that cannot be inferred
const configurations: Record = {};
@@ -221,10 +249,29 @@ function CheckoutContent() {
...(Object.keys(configurations).length > 0 && { configurations }),
};
+ console.log("🚀 Submitting order with data:", JSON.stringify(orderData, null, 2));
+ console.log("🚀 Address confirmed state:", addressConfirmed);
+ console.log("🚀 Confirmed address:", confirmedAddress);
+ console.log("🚀 Order type:", orderType);
+ console.log("🚀 SKUs:", skus);
+
const response = await authenticatedApi.post<{ sfOrderId: string }>("/orders", orderData);
router.push(`/orders/${response.sfOrderId}?status=success`);
} catch (error) {
- console.error("Order submission failed:", error);
+ console.error("🚨 Order submission failed:", error);
+
+ // Enhanced error logging for debugging
+ if (error instanceof Error) {
+ console.error("🚨 Error name:", error.name);
+ console.error("🚨 Error message:", error.message);
+ console.error("🚨 Error stack:", error.stack);
+ }
+
+ // If it's an API error, try to get more details
+ if (error && typeof error === 'object' && 'status' in error) {
+ console.error("🚨 HTTP Status:", error.status);
+ console.error("🚨 Error details:", error);
+ }
let errorMessage = "Order submission failed";
if (error instanceof Error) {
@@ -240,18 +287,25 @@ function CheckoutContent() {
}
};
- const handleAddressConfirmed = (address?: Address) => {
+ const handleAddressConfirmed = useCallback((address?: Address) => {
console.log("🎯 PARENT: handleAddressConfirmed called with:", address);
console.log("🎯 PARENT: Current addressConfirmed state before:", addressConfirmed);
- setAddressConfirmed(true);
- setConfirmedAddress(address || null);
- console.log("🎯 PARENT: addressConfirmed state set to true");
- // Force a log after state update (in next tick)
- setTimeout(() => {
- console.log("🎯 PARENT: addressConfirmed state after update:", addressConfirmed);
- }, 0);
- };
+ console.log("🎯 PARENT: About to call setAddressConfirmed(true)...");
+ setAddressConfirmed(prev => {
+ console.log("🎯 PARENT: setAddressConfirmed functional update - prev:", prev, "-> true");
+ return true;
+ });
+ console.log("🎯 PARENT: setAddressConfirmed(true) called");
+
+ console.log("🎯 PARENT: About to call setConfirmedAddress...");
+ setConfirmedAddress(address || null);
+ console.log("🎯 PARENT: setConfirmedAddress called");
+
+ // Force a re-render to ensure the UI updates
+ setForceUpdate(prev => prev + 1);
+ console.log("🎯 PARENT: Force update triggered");
+ }, [addressConfirmed]);
const handleAddressIncomplete = () => {
setAddressConfirmed(false);
@@ -432,11 +486,13 @@ function CheckoutContent() {
)}
- {/* Debug Info - Remove in production */}
-
-
Debug Info: Address Confirmed: {addressConfirmed ? '✅' : '❌'} ({String(addressConfirmed)}) |
- Payment Methods: {paymentMethodsLoading ? '⏳ Loading...' : paymentMethodsError ? '❌ Error' : paymentMethods ? `✅ ${paymentMethods.paymentMethods.length} found` : '❌ None'} |
+ {/* Debug Info - Remove in production */}
+
+ Debug Info: Address Confirmed: {addressConfirmed ? '✅ TRUE' : '❌ FALSE'} |
+ Order Type: {orderType} |
Order Items: {checkoutState.orderItems.length} |
+ Payment Methods: {paymentMethodsLoading ? '⏳ Loading...' : paymentMethodsError ? '❌ Error' : paymentMethods ? `✅ ${paymentMethods.paymentMethods.length} found` : '❌ None'} |
+ Force Update: {forceUpdate} |
Can Submit: {!(
submitting ||
checkoutState.orderItems.length === 0 ||
@@ -444,8 +500,7 @@ function CheckoutContent() {
paymentMethodsLoading ||
!paymentMethods ||
paymentMethods.paymentMethods.length === 0
- ) ? '✅' : '❌'} |
- Render Time: {new Date().toLocaleTimeString()}
+ ) ? '✅ YES' : '❌ NO'}
diff --git a/apps/portal/src/components/checkout/address-confirmation.tsx b/apps/portal/src/components/checkout/address-confirmation.tsx
index 278754cd..71f6bcbd 100644
--- a/apps/portal/src/components/checkout/address-confirmation.tsx
+++ b/apps/portal/src/components/checkout/address-confirmation.tsx
@@ -31,19 +31,38 @@ interface AddressConfirmationProps {
onAddressConfirmed: (address?: Address) => void;
onAddressIncomplete: () => void;
orderType?: string; // Add order type to customize behavior
+ // Optional controlled props for parent state management
+ addressConfirmed?: boolean; // If provided, use this instead of internal state
+ onAddressConfirmationChange?: (confirmed: boolean) => void; // Callback for controlled mode
}
export function AddressConfirmation({
onAddressConfirmed,
onAddressIncomplete,
orderType,
+ addressConfirmed: controlledAddressConfirmed,
+ onAddressConfirmationChange,
}: AddressConfirmationProps) {
const [billingInfo, setBillingInfo] = useState
(null);
const [loading, setLoading] = useState(true);
const [editing, setEditing] = useState(false);
const [editedAddress, setEditedAddress] = useState(null);
const [error, setError] = useState(null);
- const [addressConfirmed, setAddressConfirmed] = useState(false);
+ const [internalAddressConfirmed, setInternalAddressConfirmed] = useState(false);
+
+ // Use controlled prop if provided, otherwise use internal state
+ const addressConfirmed = controlledAddressConfirmed ?? internalAddressConfirmed;
+ const setAddressConfirmed = (value: boolean | ((prev: boolean) => boolean)) => {
+ const newValue = typeof value === 'function' ? value(addressConfirmed) : value;
+
+ if (controlledAddressConfirmed !== undefined && onAddressConfirmationChange) {
+ // Controlled mode: notify parent
+ onAddressConfirmationChange(newValue);
+ } else {
+ // Uncontrolled mode: update internal state
+ setInternalAddressConfirmed(newValue);
+ }
+ };
const isInternetOrder = orderType === "Internet";
const requiresAddressVerification = isInternetOrder;
@@ -57,20 +76,33 @@ export function AddressConfirmation({
// Since address is required at signup, it should always be complete
// But we still need verification for Internet orders
if (requiresAddressVerification) {
- // For Internet orders, don't auto-confirm - require explicit verification
- setAddressConfirmed(false);
- onAddressIncomplete(); // Keep disabled until explicitly confirmed
+ // For Internet orders, only reset confirmation state if not already confirmed
+ // This prevents clobbering existing confirmation on re-renders/re-fetches
+ if (!addressConfirmed) {
+ console.log("🏠 Internet order: Setting initial unconfirmed state");
+ setAddressConfirmed(false);
+ onAddressIncomplete(); // Keep disabled until explicitly confirmed
+ } else {
+ console.log("🏠 Internet order: Preserving existing confirmation state");
+ // Address is already confirmed, don't clobber the state
+ }
} else {
// For other order types, auto-confirm since address exists from signup
- onAddressConfirmed(data.address);
- setAddressConfirmed(true);
+ // Only call parent callback if we're not already confirmed to avoid spam
+ if (!addressConfirmed) {
+ console.log("🏠 Non-Internet order: Auto-confirming address");
+ onAddressConfirmed(data.address);
+ setAddressConfirmed(true);
+ } else {
+ console.log("🏠 Non-Internet order: Already confirmed, skipping callback");
+ }
}
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to load address");
} finally {
setLoading(false);
}
- }, [requiresAddressVerification, onAddressIncomplete, onAddressConfirmed]);
+ }, [requiresAddressVerification, onAddressIncomplete, onAddressConfirmed, addressConfirmed]);
useEffect(() => {
void fetchBillingInfo();
@@ -109,11 +141,11 @@ export function AddressConfirmation({
try {
setError(null);
- // Use the edited address for the order (will be flagged as changed)
- onAddressConfirmed(editedAddress);
+
+ // UX-FIRST: Update UI immediately
setEditing(false);
setAddressConfirmed(true);
-
+
// Update local state to show the new address
if (billingInfo) {
setBillingInfo({
@@ -122,25 +154,39 @@ export function AddressConfirmation({
isComplete: true,
});
}
+
+ // SIDE-EFFECT SECOND: Use the edited address for the order (will be flagged as changed)
+ onAddressConfirmed(editedAddress);
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to update address");
}
};
- const handleConfirmAddress = () => {
+ const handleConfirmAddress = (e: React.MouseEvent) => {
+ // Prevent any default behavior and event propagation
+ e.preventDefault();
+ e.stopPropagation();
+
console.log("🏠 CONFIRM ADDRESS CLICKED", {
billingInfo,
hasAddress: !!billingInfo?.address,
- address: billingInfo?.address
+ address: billingInfo?.address,
+ currentAddressConfirmed: addressConfirmed
});
if (billingInfo?.address) {
- console.log("🏠 Calling onAddressConfirmed with:", billingInfo.address);
- onAddressConfirmed(billingInfo.address);
+ console.log("🏠 UX-First approach: Updating local state immediately for instant UI feedback");
+
+ // UX-FIRST: Update local state immediately for instant UI response
setAddressConfirmed(true);
- console.log("🏠 Address confirmed state set to true");
+ console.log("🏠 ✅ Local addressConfirmed set to true (UI will update immediately)");
+
+ // SIDE-EFFECT SECOND: Notify parent after local state update
+ console.log("🏠 Notifying parent component...");
+ onAddressConfirmed(billingInfo.address);
+ console.log("🏠 ✅ Parent onAddressConfirmed() called with:", billingInfo.address);
} else {
- console.log("🏠 No billing info or address available");
+ console.log("🏠 ❌ No billing info or address available");
}
};
@@ -170,6 +216,7 @@ export function AddressConfirmation({
Address Error
{error}