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: {
|
||||
street: process.env.ORDER_BILL_TO_STREET_FIELD || "BillToStreet",
|
||||
city: process.env.ORDER_BILL_TO_CITY_FIELD || "BillToCity",
|
||||
state: process.env.ORDER_BILL_TO_STATE_FIELD || "BillToState",
|
||||
postalCode: process.env.ORDER_BILL_TO_POSTAL_CODE_FIELD || "BillToPostalCode",
|
||||
country: process.env.ORDER_BILL_TO_COUNTRY_FIELD || "BillToCountry",
|
||||
// Default to standard Order BillingAddress components
|
||||
// Env overrides maintain backward compatibility if orgs used custom fields
|
||||
street: process.env.ORDER_BILL_TO_STREET_FIELD || "BillingStreet",
|
||||
city: process.env.ORDER_BILL_TO_CITY_FIELD || "BillingCity",
|
||||
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: {
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useParams, useSearchParams } from "next/navigation";
|
||||
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 { StatusPill } from "@/components/ui/status-pill";
|
||||
import { authenticatedApi } from "@/lib/api";
|
||||
@ -71,8 +71,8 @@ const getDetailedStatusInfo = (
|
||||
color: "text-blue-800",
|
||||
bgColor: "bg-blue-50 border-blue-200",
|
||||
description: "Our team is reviewing your order details",
|
||||
nextAction: "We'll contact you within 1-2 business days with next steps",
|
||||
timeline: "Review typically takes 1-2 business days",
|
||||
nextAction: "We will contact you within 1 business day with next steps",
|
||||
timeline: "Review typically takes 1 business day",
|
||||
};
|
||||
}
|
||||
|
||||
@ -111,20 +111,20 @@ const getDetailedStatusInfo = (
|
||||
color: "text-gray-800",
|
||||
bgColor: "bg-gray-50 border-gray-200",
|
||||
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) => {
|
||||
switch (orderType) {
|
||||
case "Internet":
|
||||
return "🌐";
|
||||
return <WifiIcon className="h-6 w-6" />;
|
||||
case "SIM":
|
||||
return "📱";
|
||||
return <DevicePhoneMobileIcon className="h-6 w-6" />;
|
||||
case "VPN":
|
||||
return "🔒";
|
||||
return <LockClosedIcon className="h-6 w-6" />;
|
||||
default:
|
||||
return "📦";
|
||||
return <CubeIcon className="h-6 w-6" />;
|
||||
}
|
||||
};
|
||||
|
||||
@ -182,7 +182,7 @@ export default function OrderStatusPage() {
|
||||
|
||||
{/* Success Banner for New Orders */}
|
||||
{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">
|
||||
<CheckCircleIcon className="h-6 w-6 text-green-600 mt-0.5 mr-3 flex-shrink-0" />
|
||||
<div>
|
||||
@ -190,7 +190,7 @@ export default function OrderStatusPage() {
|
||||
Order Submitted Successfully!
|
||||
</h3>
|
||||
<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.
|
||||
</p>
|
||||
<div className="text-sm text-green-700">
|
||||
@ -198,9 +198,9 @@ export default function OrderStatusPage() {
|
||||
<strong>What happens next:</strong>
|
||||
</p>
|
||||
<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>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>
|
||||
</ul>
|
||||
</div>
|
||||
@ -209,8 +209,8 @@ export default function OrderStatusPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Service Overview */}
|
||||
{data &&
|
||||
{/* Status Section - Moved to top */}
|
||||
{data && (
|
||||
(() => {
|
||||
const statusInfo = getDetailedStatusInfo(
|
||||
data.status,
|
||||
@ -218,7 +218,6 @@ export default function OrderStatusPage() {
|
||||
data.activationType,
|
||||
data.scheduledAt
|
||||
);
|
||||
const serviceIcon = getServiceTypeIcon(data.orderType);
|
||||
|
||||
const statusVariant = statusInfo.label.includes("Active")
|
||||
? "success"
|
||||
@ -229,268 +228,269 @@ export default function OrderStatusPage() {
|
||||
: "neutral";
|
||||
|
||||
return (
|
||||
<div className="bg-white border rounded-2xl p-8 mb-8">
|
||||
{/* Service Header */}
|
||||
<div className="flex items-start gap-6 mb-6">
|
||||
<div className="text-4xl">{serviceIcon}</div>
|
||||
<div className="flex-1">
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-2">
|
||||
{data.orderType} Service
|
||||
</h2>
|
||||
<p className="text-gray-600 mb-4">
|
||||
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-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>
|
||||
);
|
||||
})()}
|
||||
<SubCard
|
||||
className="mb-9"
|
||||
header={
|
||||
<h3 className="text-xl font-bold text-gray-900">Status</h3>
|
||||
}
|
||||
>
|
||||
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3 mb-6">
|
||||
<div className="text-gray-700 text-lg sm:text-xl">{statusInfo.description}</div>
|
||||
<StatusPill
|
||||
label={statusInfo.label}
|
||||
variant={statusVariant as "info" | "success" | "warning" | "error"}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Status Card (standardized) */}
|
||||
<SubCard
|
||||
title="Status"
|
||||
right={
|
||||
<StatusPill
|
||||
label={statusInfo.label}
|
||||
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>
|
||||
|
||||
{/* Highlighted Next Steps Section */}
|
||||
{statusInfo.nextAction && (
|
||||
<div className="bg-blue-50 border-2 border-blue-200 rounded-xl p-4 mb-4 shadow-sm">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<div className="w-2 h-2 bg-blue-500 rounded-full animate-pulse"></div>
|
||||
<p className="font-bold text-blue-900 text-base">Next Steps</p>
|
||||
</div>
|
||||
)}
|
||||
{statusInfo.timeline && (
|
||||
<p className="text-blue-800 text-base leading-relaxed">{statusInfo.nextAction}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{statusInfo.timeline && (
|
||||
<div className="bg-gray-50 rounded-lg p-3 border border-gray-200">
|
||||
<p className="text-sm text-gray-600">
|
||||
<span className="font-medium">Timeline:</span> {statusInfo.timeline}
|
||||
</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>
|
||||
</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>
|
||||
);
|
||||
})()}
|
||||
})()
|
||||
)}
|
||||
|
||||
{/* 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 */}
|
||||
<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>
|
||||
<p className="text-gray-700 text-sm">
|
||||
Questions about your order? Contact our support team.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<div className="flex gap-2 w-full sm:w-auto sm:justify-end">
|
||||
<a
|
||||
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
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
import { useEffect, useState, Suspense } from "react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
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 { authenticatedApi } from "@/lib/api";
|
||||
|
||||
@ -92,7 +92,7 @@ export default function OrdersPage() {
|
||||
color: "text-blue-800",
|
||||
bgColor: "bg-blue-100",
|
||||
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) => {
|
||||
switch (orderType) {
|
||||
case "Internet":
|
||||
return { icon: "🌐", label: "Internet Service" };
|
||||
return { icon: <WifiIcon className="h-6 w-6" />, label: "Internet Service" };
|
||||
case "SIM":
|
||||
return { icon: "📱", label: "Mobile Service" };
|
||||
return { icon: <DevicePhoneMobileIcon className="h-6 w-6" />, label: "Mobile Service" };
|
||||
case "VPN":
|
||||
return { icon: "🔒", label: "VPN Service" };
|
||||
return { icon: <LockClosedIcon className="h-6 w-6" />, label: "VPN Service" };
|
||||
default:
|
||||
return { icon: "📦", label: "Service" };
|
||||
return { icon: <CubeIcon className="h-6 w-6" />, label: "Service" };
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user