Update field mapping for billing address and enhance order page components
- Updated default field mappings for billing address in SalesforceFieldMap to maintain backward compatibility. - Added new icons for service types in OrdersPage and OrderStatusPage for improved visual representation. - Revised next action and timeline messages for clarity and consistency across order status components. - Enhanced layout and styling for better user experience in order details and service overview sections.
This commit is contained in:
parent
22baa0af06
commit
75e205b1e3
@ -176,11 +176,13 @@ export function getSalesforceFieldMap(): SalesforceFieldMap {
|
|||||||
|
|
||||||
// Billing address snapshot fields
|
// Billing address snapshot fields
|
||||||
billing: {
|
billing: {
|
||||||
street: process.env.ORDER_BILL_TO_STREET_FIELD || "BillToStreet",
|
// Default to standard Order BillingAddress components
|
||||||
city: process.env.ORDER_BILL_TO_CITY_FIELD || "BillToCity",
|
// Env overrides maintain backward compatibility if orgs used custom fields
|
||||||
state: process.env.ORDER_BILL_TO_STATE_FIELD || "BillToState",
|
street: process.env.ORDER_BILL_TO_STREET_FIELD || "BillingStreet",
|
||||||
postalCode: process.env.ORDER_BILL_TO_POSTAL_CODE_FIELD || "BillToPostalCode",
|
city: process.env.ORDER_BILL_TO_CITY_FIELD || "BillingCity",
|
||||||
country: process.env.ORDER_BILL_TO_COUNTRY_FIELD || "BillToCountry",
|
state: process.env.ORDER_BILL_TO_STATE_FIELD || "BillingState",
|
||||||
|
postalCode: process.env.ORDER_BILL_TO_POSTAL_CODE_FIELD || "BillingPostalCode",
|
||||||
|
country: process.env.ORDER_BILL_TO_COUNTRY_FIELD || "BillingCountry",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
orderItem: {
|
orderItem: {
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useParams, useSearchParams } from "next/navigation";
|
import { useParams, useSearchParams } from "next/navigation";
|
||||||
import { PageLayout } from "@/components/layout/page-layout";
|
import { PageLayout } from "@/components/layout/page-layout";
|
||||||
import { ClipboardDocumentCheckIcon, CheckCircleIcon } from "@heroicons/react/24/outline";
|
import { ClipboardDocumentCheckIcon, CheckCircleIcon, WifiIcon, DevicePhoneMobileIcon, LockClosedIcon, CubeIcon, StarIcon, WrenchScrewdriverIcon, PlusIcon, BoltIcon, ExclamationTriangleIcon, EnvelopeIcon, PhoneIcon } from "@heroicons/react/24/outline";
|
||||||
import { SubCard } from "@/components/ui/sub-card";
|
import { SubCard } from "@/components/ui/sub-card";
|
||||||
import { StatusPill } from "@/components/ui/status-pill";
|
import { StatusPill } from "@/components/ui/status-pill";
|
||||||
import { authenticatedApi } from "@/lib/api";
|
import { authenticatedApi } from "@/lib/api";
|
||||||
@ -71,8 +71,8 @@ const getDetailedStatusInfo = (
|
|||||||
color: "text-blue-800",
|
color: "text-blue-800",
|
||||||
bgColor: "bg-blue-50 border-blue-200",
|
bgColor: "bg-blue-50 border-blue-200",
|
||||||
description: "Our team is reviewing your order details",
|
description: "Our team is reviewing your order details",
|
||||||
nextAction: "We'll contact you within 1-2 business days with next steps",
|
nextAction: "We will contact you within 1 business day with next steps",
|
||||||
timeline: "Review typically takes 1-2 business days",
|
timeline: "Review typically takes 1 business day",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,20 +111,20 @@ const getDetailedStatusInfo = (
|
|||||||
color: "text-gray-800",
|
color: "text-gray-800",
|
||||||
bgColor: "bg-gray-50 border-gray-200",
|
bgColor: "bg-gray-50 border-gray-200",
|
||||||
description: "Your order is being processed",
|
description: "Your order is being processed",
|
||||||
timeline: "We'll update you as progress is made",
|
timeline: "We will update you as progress is made",
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getServiceTypeIcon = (orderType?: string) => {
|
const getServiceTypeIcon = (orderType?: string) => {
|
||||||
switch (orderType) {
|
switch (orderType) {
|
||||||
case "Internet":
|
case "Internet":
|
||||||
return "🌐";
|
return <WifiIcon className="h-6 w-6" />;
|
||||||
case "SIM":
|
case "SIM":
|
||||||
return "📱";
|
return <DevicePhoneMobileIcon className="h-6 w-6" />;
|
||||||
case "VPN":
|
case "VPN":
|
||||||
return "🔒";
|
return <LockClosedIcon className="h-6 w-6" />;
|
||||||
default:
|
default:
|
||||||
return "📦";
|
return <CubeIcon className="h-6 w-6" />;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -182,7 +182,7 @@ export default function OrderStatusPage() {
|
|||||||
|
|
||||||
{/* Success Banner for New Orders */}
|
{/* Success Banner for New Orders */}
|
||||||
{isNewOrder && (
|
{isNewOrder && (
|
||||||
<div className="bg-green-50 border border-green-200 rounded-xl p-6 mb-6">
|
<div className="bg-green-50 border border-green-200 rounded-xl p-4 sm:p-6 mb-6">
|
||||||
<div className="flex items-start">
|
<div className="flex items-start">
|
||||||
<CheckCircleIcon className="h-6 w-6 text-green-600 mt-0.5 mr-3 flex-shrink-0" />
|
<CheckCircleIcon className="h-6 w-6 text-green-600 mt-0.5 mr-3 flex-shrink-0" />
|
||||||
<div>
|
<div>
|
||||||
@ -190,7 +190,7 @@ export default function OrderStatusPage() {
|
|||||||
Order Submitted Successfully!
|
Order Submitted Successfully!
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-green-800 mb-3">
|
<p className="text-green-800 mb-3">
|
||||||
Your order has been created and submitted for processing. We'll notify you as
|
Your order has been created and submitted for processing. We will notify you as
|
||||||
soon as it's approved and ready for activation.
|
soon as it's approved and ready for activation.
|
||||||
</p>
|
</p>
|
||||||
<div className="text-sm text-green-700">
|
<div className="text-sm text-green-700">
|
||||||
@ -198,9 +198,9 @@ export default function OrderStatusPage() {
|
|||||||
<strong>What happens next:</strong>
|
<strong>What happens next:</strong>
|
||||||
</p>
|
</p>
|
||||||
<ul className="list-disc list-inside space-y-1 ml-4">
|
<ul className="list-disc list-inside space-y-1 ml-4">
|
||||||
<li>Our team will review your order (usually within 1-2 business days)</li>
|
<li>Our team will review your order (within 1 business day)</li>
|
||||||
<li>You'll receive an email confirmation once approved</li>
|
<li>You'll receive an email confirmation once approved</li>
|
||||||
<li>We'll schedule activation based on your preferences</li>
|
<li>We will schedule activation based on your preferences</li>
|
||||||
<li>This page will update automatically as your order progresses</li>
|
<li>This page will update automatically as your order progresses</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@ -209,8 +209,8 @@ export default function OrderStatusPage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Service Overview */}
|
{/* Status Section - Moved to top */}
|
||||||
{data &&
|
{data && (
|
||||||
(() => {
|
(() => {
|
||||||
const statusInfo = getDetailedStatusInfo(
|
const statusInfo = getDetailedStatusInfo(
|
||||||
data.status,
|
data.status,
|
||||||
@ -218,7 +218,6 @@ export default function OrderStatusPage() {
|
|||||||
data.activationType,
|
data.activationType,
|
||||||
data.scheduledAt
|
data.scheduledAt
|
||||||
);
|
);
|
||||||
const serviceIcon = getServiceTypeIcon(data.orderType);
|
|
||||||
|
|
||||||
const statusVariant = statusInfo.label.includes("Active")
|
const statusVariant = statusInfo.label.includes("Active")
|
||||||
? "success"
|
? "success"
|
||||||
@ -229,268 +228,269 @@ export default function OrderStatusPage() {
|
|||||||
: "neutral";
|
: "neutral";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-white border rounded-2xl p-8 mb-8">
|
<SubCard
|
||||||
{/* Service Header */}
|
className="mb-9"
|
||||||
<div className="flex items-start gap-6 mb-6">
|
header={
|
||||||
<div className="text-4xl">{serviceIcon}</div>
|
<h3 className="text-xl font-bold text-gray-900">Status</h3>
|
||||||
<div className="flex-1">
|
}
|
||||||
<h2 className="text-2xl font-bold text-gray-900 mb-2">
|
>
|
||||||
{data.orderType} Service
|
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3 mb-6">
|
||||||
</h2>
|
<div className="text-gray-700 text-lg sm:text-xl">{statusInfo.description}</div>
|
||||||
<p className="text-gray-600 mb-4">
|
<StatusPill
|
||||||
Order #{data.orderNumber || data.id.slice(-8)} • Placed{" "}
|
label={statusInfo.label}
|
||||||
{new Date(data.createdDate).toLocaleDateString("en-US", {
|
variant={statusVariant as "info" | "success" | "warning" | "error"}
|
||||||
weekday: "long",
|
/>
|
||||||
month: "long",
|
|
||||||
day: "numeric",
|
|
||||||
year: "numeric",
|
|
||||||
})}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{data.items &&
|
|
||||||
data.items.length > 0 &&
|
|
||||||
(() => {
|
|
||||||
const totals = calculateDetailedTotals(data.items);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="text-right">
|
|
||||||
<div className="space-y-2">
|
|
||||||
{totals.monthlyTotal > 0 && (
|
|
||||||
<div>
|
|
||||||
<p className="text-3xl font-bold text-gray-900">
|
|
||||||
¥{totals.monthlyTotal.toLocaleString()}
|
|
||||||
</p>
|
|
||||||
<p className="text-gray-500">per month</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{totals.oneTimeTotal > 0 && (
|
|
||||||
<div className="mt-2">
|
|
||||||
<p className="text-2xl font-bold text-orange-600">
|
|
||||||
¥{totals.oneTimeTotal.toLocaleString()}
|
|
||||||
</p>
|
|
||||||
<p className="text-gray-500">one-time</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Fallback to TotalAmount if no items or calculation fails */}
|
|
||||||
{totals.monthlyTotal === 0 &&
|
|
||||||
totals.oneTimeTotal === 0 &&
|
|
||||||
data.totalAmount && (
|
|
||||||
<div>
|
|
||||||
<p className="text-3xl font-bold text-gray-900">
|
|
||||||
¥{data.totalAmount.toLocaleString()}
|
|
||||||
</p>
|
|
||||||
<p className="text-gray-500">total amount</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})()}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Status Card (standardized) */}
|
{/* Highlighted Next Steps Section */}
|
||||||
<SubCard
|
{statusInfo.nextAction && (
|
||||||
title="Status"
|
<div className="bg-blue-50 border-2 border-blue-200 rounded-xl p-4 mb-4 shadow-sm">
|
||||||
right={
|
<div className="flex items-center gap-2 mb-2">
|
||||||
<StatusPill
|
<div className="w-2 h-2 bg-blue-500 rounded-full animate-pulse"></div>
|
||||||
label={statusInfo.label}
|
<p className="font-bold text-blue-900 text-base">Next Steps</p>
|
||||||
variant={statusVariant as "info" | "success" | "warning" | "error"}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className="text-gray-700 mb-2">{statusInfo.description}</div>
|
|
||||||
{statusInfo.nextAction && (
|
|
||||||
<div className="bg-gray-50 rounded-lg p-3 mb-3 border border-gray-200">
|
|
||||||
<p className="font-medium text-gray-900 text-sm">Next Steps</p>
|
|
||||||
<p className="text-gray-700 text-sm">{statusInfo.nextAction}</p>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
<p className="text-blue-800 text-base leading-relaxed">{statusInfo.nextAction}</p>
|
||||||
{statusInfo.timeline && (
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{statusInfo.timeline && (
|
||||||
|
<div className="bg-gray-50 rounded-lg p-3 border border-gray-200">
|
||||||
<p className="text-sm text-gray-600">
|
<p className="text-sm text-gray-600">
|
||||||
<span className="font-medium">Timeline:</span> {statusInfo.timeline}
|
<span className="font-medium">Timeline:</span> {statusInfo.timeline}
|
||||||
</p>
|
</p>
|
||||||
)}
|
|
||||||
</SubCard>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})()}
|
|
||||||
|
|
||||||
{/* Service Details */}
|
|
||||||
{data?.items && data.items.length > 0 && (
|
|
||||||
<SubCard title="Your Services & Products">
|
|
||||||
<div className="space-y-3">
|
|
||||||
{data.items.map(item => {
|
|
||||||
// Use the actual Item_Class__c values from Salesforce documentation
|
|
||||||
const itemClass = item.product.itemClass;
|
|
||||||
|
|
||||||
// Get appropriate icon and color based on actual item class
|
|
||||||
const getItemTypeInfo = () => {
|
|
||||||
switch (itemClass) {
|
|
||||||
case "Service":
|
|
||||||
return {
|
|
||||||
icon: "⭐",
|
|
||||||
bg: "bg-blue-50 border-blue-200",
|
|
||||||
iconBg: "bg-blue-100 text-blue-600",
|
|
||||||
label: "Service",
|
|
||||||
labelColor: "text-blue-600",
|
|
||||||
};
|
|
||||||
case "Installation":
|
|
||||||
return {
|
|
||||||
icon: "🔧",
|
|
||||||
bg: "bg-orange-50 border-orange-200",
|
|
||||||
iconBg: "bg-orange-100 text-orange-600",
|
|
||||||
label: "Installation",
|
|
||||||
labelColor: "text-orange-600",
|
|
||||||
};
|
|
||||||
case "Add-on":
|
|
||||||
return {
|
|
||||||
icon: "+",
|
|
||||||
bg: "bg-green-50 border-green-200",
|
|
||||||
iconBg: "bg-green-100 text-green-600",
|
|
||||||
label: "Add-on",
|
|
||||||
labelColor: "text-green-600",
|
|
||||||
};
|
|
||||||
case "Activation":
|
|
||||||
return {
|
|
||||||
icon: "⚡",
|
|
||||||
bg: "bg-purple-50 border-purple-200",
|
|
||||||
iconBg: "bg-purple-100 text-purple-600",
|
|
||||||
label: "Activation",
|
|
||||||
labelColor: "text-purple-600",
|
|
||||||
};
|
|
||||||
default:
|
|
||||||
return {
|
|
||||||
icon: "📦",
|
|
||||||
bg: "bg-gray-50 border-gray-200",
|
|
||||||
iconBg: "bg-gray-100 text-gray-600",
|
|
||||||
label: itemClass || "Other",
|
|
||||||
labelColor: "text-gray-600",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const typeInfo = getItemTypeInfo();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div key={item.id} className={`rounded-lg p-4 border ${typeInfo.bg}`}>
|
|
||||||
<div className="flex justify-between items-start">
|
|
||||||
<div className="flex items-start gap-3 flex-1">
|
|
||||||
<div
|
|
||||||
className={`w-8 h-8 rounded-full flex items-center justify-center text-sm ${typeInfo.iconBg} flex-shrink-0`}
|
|
||||||
>
|
|
||||||
{typeInfo.icon}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex-1 min-w-0">
|
|
||||||
<div className="flex items-center gap-2 mb-1">
|
|
||||||
<h3 className="font-semibold text-gray-900 truncate">
|
|
||||||
{item.product.name}
|
|
||||||
</h3>
|
|
||||||
<span
|
|
||||||
className={`text-xs px-2 py-1 rounded-full font-medium ${typeInfo.bg} ${typeInfo.labelColor} flex-shrink-0`}
|
|
||||||
>
|
|
||||||
{typeInfo.label}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-wrap items-center gap-3 text-sm text-gray-600">
|
|
||||||
<span className="font-medium">{item.product.billingCycle}</span>
|
|
||||||
{item.quantity > 1 && <span>Qty: {item.quantity}</span>}
|
|
||||||
{item.product.itemClass && (
|
|
||||||
<span className="text-xs bg-gray-100 px-2 py-1 rounded">
|
|
||||||
{item.product.itemClass}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="text-right ml-3 flex-shrink-0">
|
|
||||||
{item.totalPrice && (
|
|
||||||
<div className="font-semibold text-gray-900">
|
|
||||||
¥{item.totalPrice.toLocaleString()}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="text-xs text-gray-500">
|
|
||||||
{item.product.billingCycle === "Monthly" ? "/month" : "one-time"}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
)}
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</SubCard>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Pricing Summary */}
|
|
||||||
{data?.items &&
|
|
||||||
data.items.length > 0 &&
|
|
||||||
(() => {
|
|
||||||
const totals = calculateDetailedTotals(data.items);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SubCard title="Pricing Summary">
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
|
||||||
{totals.monthlyTotal > 0 && (
|
|
||||||
<div className="bg-blue-50 rounded-lg p-4 text-center">
|
|
||||||
<p className="text-2xl font-bold text-blue-600">
|
|
||||||
¥{totals.monthlyTotal.toLocaleString()}
|
|
||||||
</p>
|
|
||||||
<p className="text-sm text-gray-600">Monthly Charges</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{totals.oneTimeTotal > 0 && (
|
|
||||||
<div className="bg-orange-50 rounded-lg p-4 text-center">
|
|
||||||
<p className="text-2xl font-bold text-orange-600">
|
|
||||||
¥{totals.oneTimeTotal.toLocaleString()}
|
|
||||||
</p>
|
|
||||||
<p className="text-sm text-gray-600">One-time Charges</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Compact Fee Disclaimer */}
|
|
||||||
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-3">
|
|
||||||
<div className="flex items-start gap-2">
|
|
||||||
<span className="text-yellow-600 text-sm">⚠️</span>
|
|
||||||
<div>
|
|
||||||
<p className="text-sm font-medium text-yellow-900">Additional fees may apply</p>
|
|
||||||
<p className="text-xs text-yellow-800 mt-1">
|
|
||||||
Weekend installation (+¥3,000), express setup, or special configuration
|
|
||||||
charges may be added. We'll contact you before applying any additional
|
|
||||||
fees.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</SubCard>
|
</SubCard>
|
||||||
);
|
);
|
||||||
})()}
|
})()
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Combined Service Overview and Products */}
|
||||||
|
{data && (
|
||||||
|
<div className="bg-white border rounded-2xl p-4 sm:p-8 mb-8">
|
||||||
|
{/* Service Header */}
|
||||||
|
<div className="flex flex-col sm:flex-row items-start gap-4 sm:gap-6 mb-6">
|
||||||
|
<div className="flex items-center text-3xl sm:text-4xl">{getServiceTypeIcon(data.orderType)}</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<h2 className="text-2xl font-bold text-gray-900 mb-2 flex items-center">
|
||||||
|
{data.orderType} Service
|
||||||
|
</h2>
|
||||||
|
<p className="text-gray-600 mb-4 text-sm sm:text-base">
|
||||||
|
Order #{data.orderNumber || data.id.slice(-8)} • Placed{" "}
|
||||||
|
{new Date(data.createdDate).toLocaleDateString("en-US", {
|
||||||
|
weekday: "long",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
year: "numeric",
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{data.items &&
|
||||||
|
data.items.length > 0 &&
|
||||||
|
(() => {
|
||||||
|
const totals = calculateDetailedTotals(data.items);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="text-left sm:text-right w-full sm:w-auto mt-2 sm:mt-0">
|
||||||
|
<div className="space-y-2 sm:space-y-2">
|
||||||
|
{totals.monthlyTotal > 0 && (
|
||||||
|
<div>
|
||||||
|
<p className="text-2xl sm:text-3xl font-bold text-gray-900 tabular-nums">
|
||||||
|
¥{totals.monthlyTotal.toLocaleString()}
|
||||||
|
</p>
|
||||||
|
<p className="text-sm text-gray-500">per month</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{totals.oneTimeTotal > 0 && (
|
||||||
|
<div className="mt-2">
|
||||||
|
<p className="text-2xl sm:text-3xl font-bold text-orange-600 tabular-nums">
|
||||||
|
¥{totals.oneTimeTotal.toLocaleString()}
|
||||||
|
</p>
|
||||||
|
<p className="text-sm text-gray-500">one-time</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Fallback to TotalAmount if no items or calculation fails */}
|
||||||
|
{totals.monthlyTotal === 0 &&
|
||||||
|
totals.oneTimeTotal === 0 &&
|
||||||
|
data.totalAmount && (
|
||||||
|
<div>
|
||||||
|
<p className="text-2xl sm:text-3xl font-bold text-gray-900 tabular-nums">
|
||||||
|
¥{data.totalAmount.toLocaleString()}
|
||||||
|
</p>
|
||||||
|
<p className="text-sm text-gray-500">total amount</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Services & Products Section */}
|
||||||
|
{data?.items && data.items.length > 0 && (
|
||||||
|
<div className="border-t pt-6">
|
||||||
|
<h3 className="text-lg font-semibold text-gray-900 mb-4">Your Services & Products</h3>
|
||||||
|
<div className="space-y-3">
|
||||||
|
{data.items
|
||||||
|
.sort((a, b) => {
|
||||||
|
// Sort: Services first, then Installations, then others
|
||||||
|
const aIsService = a.product.itemClass === "Service";
|
||||||
|
const bIsService = b.product.itemClass === "Service";
|
||||||
|
const aIsInstallation = a.product.itemClass === "Installation";
|
||||||
|
const bIsInstallation = b.product.itemClass === "Installation";
|
||||||
|
|
||||||
|
if (aIsService && !bIsService) return -1;
|
||||||
|
if (!aIsService && bIsService) return 1;
|
||||||
|
if (aIsInstallation && !bIsInstallation) return -1;
|
||||||
|
if (!aIsInstallation && bIsInstallation) return 1;
|
||||||
|
return 0;
|
||||||
|
})
|
||||||
|
.map(item => {
|
||||||
|
// Use the actual Item_Class__c values from Salesforce documentation
|
||||||
|
const itemClass = item.product.itemClass;
|
||||||
|
|
||||||
|
// Get appropriate icon and color based on item type and billing cycle
|
||||||
|
const getItemTypeInfo = () => {
|
||||||
|
const isMonthly = item.product.billingCycle === "Monthly";
|
||||||
|
const isService = itemClass === "Service";
|
||||||
|
const isInstallation = itemClass === "Installation";
|
||||||
|
|
||||||
|
if (isService && isMonthly) {
|
||||||
|
// Main service products - Blue theme
|
||||||
|
return {
|
||||||
|
icon: <StarIcon className="h-4 w-4" />,
|
||||||
|
bg: "bg-blue-50 border-blue-200",
|
||||||
|
iconBg: "bg-blue-100 text-blue-600",
|
||||||
|
label: itemClass || "Service",
|
||||||
|
labelColor: "text-blue-600",
|
||||||
|
};
|
||||||
|
} else if (isInstallation) {
|
||||||
|
// Installation items - Green theme
|
||||||
|
return {
|
||||||
|
icon: <WrenchScrewdriverIcon className="h-4 w-4" />,
|
||||||
|
bg: "bg-green-50 border-green-200",
|
||||||
|
iconBg: "bg-green-100 text-green-600",
|
||||||
|
label: itemClass || "Installation",
|
||||||
|
labelColor: "text-green-600",
|
||||||
|
};
|
||||||
|
} else if (isMonthly) {
|
||||||
|
// Other monthly products - Blue theme
|
||||||
|
return {
|
||||||
|
icon: <StarIcon className="h-4 w-4" />,
|
||||||
|
bg: "bg-blue-50 border-blue-200",
|
||||||
|
iconBg: "bg-blue-100 text-blue-600",
|
||||||
|
label: itemClass || "Service",
|
||||||
|
labelColor: "text-blue-600",
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// One-time products - Orange theme
|
||||||
|
return {
|
||||||
|
icon: <CubeIcon className="h-4 w-4" />,
|
||||||
|
bg: "bg-orange-50 border-orange-200",
|
||||||
|
iconBg: "bg-orange-100 text-orange-600",
|
||||||
|
label: itemClass || "Add-on",
|
||||||
|
labelColor: "text-orange-600",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const typeInfo = getItemTypeInfo();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={item.id} className={`rounded-lg p-4 border ${typeInfo.bg} transition-shadow hover:shadow-sm`}>
|
||||||
|
<div className="flex flex-col sm:flex-row justify-between items-start gap-3">
|
||||||
|
<div className="flex items-start gap-3 flex-1">
|
||||||
|
<div
|
||||||
|
className={`w-8 h-8 rounded-full flex items-center justify-center text-sm ${typeInfo.iconBg} flex-shrink-0`}
|
||||||
|
>
|
||||||
|
{typeInfo.icon}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<div className="flex items-center gap-2 mb-1 flex-wrap">
|
||||||
|
<h3 className="font-semibold text-gray-900 truncate flex-1 min-w-0">
|
||||||
|
{item.product.name}
|
||||||
|
</h3>
|
||||||
|
<span
|
||||||
|
className={`text-xs px-2 py-1 rounded-full font-medium ${typeInfo.bg} ${typeInfo.labelColor}`}
|
||||||
|
>
|
||||||
|
{typeInfo.label}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-wrap items-center gap-3 text-sm text-gray-600">
|
||||||
|
<span className="font-medium">{item.product.billingCycle}</span>
|
||||||
|
{item.quantity > 1 && <span>Qty: {item.quantity}</span>}
|
||||||
|
{item.product.itemClass && (
|
||||||
|
<span className="text-xs bg-gray-100 px-2 py-1 rounded">
|
||||||
|
{item.product.itemClass}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-left sm:text-right ml-0 sm:ml-3 mt-2 sm:mt-0 flex-shrink-0 sm:w-32">
|
||||||
|
{item.totalPrice && (
|
||||||
|
<div className="font-semibold text-gray-900 tabular-nums">
|
||||||
|
¥{item.totalPrice.toLocaleString()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="text-xs text-gray-500">
|
||||||
|
{item.product.billingCycle === "Monthly" ? "/month" : "one-time"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{/* Additional fees warning */}
|
||||||
|
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-3 mt-4">
|
||||||
|
<div className="flex items-start gap-2">
|
||||||
|
<ExclamationTriangleIcon className="h-4 w-4 text-yellow-600 flex-shrink-0" />
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-medium text-yellow-900">Additional fees may apply</p>
|
||||||
|
<p className="text-xs text-yellow-800 mt-1">
|
||||||
|
Weekend installation (+¥3,000), express setup, or special configuration
|
||||||
|
charges may be added. We will contact you before applying any additional
|
||||||
|
fees.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
|
||||||
{/* Support Contact */}
|
{/* Support Contact */}
|
||||||
<SubCard title="Need Help?">
|
<SubCard title="Need Help?">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-gray-700 text-sm">
|
<p className="text-gray-700 text-sm">
|
||||||
Questions about your order? Contact our support team.
|
Questions about your order? Contact our support team.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2 w-full sm:w-auto sm:justify-end">
|
||||||
<a
|
<a
|
||||||
href="mailto:support@example.com"
|
href="mailto:support@example.com"
|
||||||
className="bg-blue-600 text-white px-3 py-2 rounded-lg text-sm font-medium hover:bg-blue-700 transition-colors"
|
className="bg-blue-600 text-white px-3 py-2 rounded-lg text-sm font-medium hover:bg-blue-700 transition-colors flex items-center gap-2 justify-center w-full sm:w-auto"
|
||||||
>
|
>
|
||||||
📧 Email
|
<EnvelopeIcon className="h-4 w-4" />
|
||||||
|
Email
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href="tel:+1234567890"
|
href="tel:+1234567890"
|
||||||
className="bg-white text-blue-600 border border-blue-600 px-3 py-2 rounded-lg text-sm font-medium hover:bg-blue-50 transition-colors"
|
className="bg-white text-blue-600 border border-blue-600 px-3 py-2 rounded-lg text-sm font-medium hover:bg-blue-50 transition-colors flex items-center gap-2 justify-center w-full sm:w-auto"
|
||||||
>
|
>
|
||||||
📞 Call
|
<PhoneIcon className="h-4 w-4" />
|
||||||
|
Call
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
import { useEffect, useState, Suspense } from "react";
|
import { useEffect, useState, Suspense } from "react";
|
||||||
import { useRouter, useSearchParams } from "next/navigation";
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
import { PageLayout } from "@/components/layout/page-layout";
|
import { PageLayout } from "@/components/layout/page-layout";
|
||||||
import { ClipboardDocumentListIcon, CheckCircleIcon } from "@heroicons/react/24/outline";
|
import { ClipboardDocumentListIcon, CheckCircleIcon, WifiIcon, DevicePhoneMobileIcon, LockClosedIcon, CubeIcon } from "@heroicons/react/24/outline";
|
||||||
import { StatusPill } from "@/components/ui/status-pill";
|
import { StatusPill } from "@/components/ui/status-pill";
|
||||||
import { authenticatedApi } from "@/lib/api";
|
import { authenticatedApi } from "@/lib/api";
|
||||||
|
|
||||||
@ -92,7 +92,7 @@ export default function OrdersPage() {
|
|||||||
color: "text-blue-800",
|
color: "text-blue-800",
|
||||||
bgColor: "bg-blue-100",
|
bgColor: "bg-blue-100",
|
||||||
description: "We're reviewing your order",
|
description: "We're reviewing your order",
|
||||||
nextAction: "We'll contact you within 1-2 business days",
|
nextAction: "We'll contact you within 1 business day",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,13 +117,13 @@ export default function OrdersPage() {
|
|||||||
const getServiceTypeDisplay = (orderType?: string) => {
|
const getServiceTypeDisplay = (orderType?: string) => {
|
||||||
switch (orderType) {
|
switch (orderType) {
|
||||||
case "Internet":
|
case "Internet":
|
||||||
return { icon: "🌐", label: "Internet Service" };
|
return { icon: <WifiIcon className="h-6 w-6" />, label: "Internet Service" };
|
||||||
case "SIM":
|
case "SIM":
|
||||||
return { icon: "📱", label: "Mobile Service" };
|
return { icon: <DevicePhoneMobileIcon className="h-6 w-6" />, label: "Mobile Service" };
|
||||||
case "VPN":
|
case "VPN":
|
||||||
return { icon: "🔒", label: "VPN Service" };
|
return { icon: <LockClosedIcon className="h-6 w-6" />, label: "VPN Service" };
|
||||||
default:
|
default:
|
||||||
return { icon: "📦", label: "Service" };
|
return { icon: <CubeIcon className="h-6 w-6" />, label: "Service" };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user