Refactor billing components for improved styling and consistency
- Updated InvoiceStatusBadge to include iconColor for better visual representation of statuses. - Enhanced BillingSummary and PaymentMethodCard components with consistent color tokens and improved layout. - Refined DataUsageChart to standardize color usage for data thresholds and loading states. - Updated SubscriptionDetails and SubscriptionStatusBadge for consistent text colors and improved user feedback. - Adjusted CardBadge styles to align with new design tokens for better visual coherence.
This commit is contained in:
parent
1f7f77775b
commit
c13e2df55f
@ -27,18 +27,18 @@ const BillingSummary = forwardRef<HTMLDivElement, BillingSummaryProps>(
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("bg-white rounded-lg border border-gray-200 p-6", className)}
|
||||
className={cn("bg-card rounded-lg border border-border p-6", className)}
|
||||
{...props}
|
||||
>
|
||||
<div className="animate-pulse">
|
||||
<div className="flex items-center mb-4">
|
||||
<div className="w-8 h-8 bg-gray-200 rounded-lg"></div>
|
||||
<div className="ml-3 h-6 bg-gray-200 rounded w-32"></div>
|
||||
<div className="w-8 h-8 bg-muted rounded-lg"></div>
|
||||
<div className="ml-3 h-6 bg-muted rounded w-32"></div>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<div className="h-4 bg-gray-200 rounded w-full"></div>
|
||||
<div className="h-4 bg-gray-200 rounded w-3/4"></div>
|
||||
<div className="h-4 bg-gray-200 rounded w-1/2"></div>
|
||||
<div className="h-4 bg-muted rounded w-full"></div>
|
||||
<div className="h-4 bg-muted rounded w-3/4"></div>
|
||||
<div className="h-4 bg-muted rounded w-1/2"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -75,7 +75,7 @@ const BillingSummary = forwardRef<HTMLDivElement, BillingSummaryProps>(
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"bg-white rounded-lg border border-gray-200 transition-all duration-200 hover:shadow-sm",
|
||||
"bg-card rounded-lg border border-border transition-all duration-200 hover:shadow-sm",
|
||||
compact ? "p-4" : "p-6",
|
||||
className
|
||||
)}
|
||||
@ -84,15 +84,15 @@ const BillingSummary = forwardRef<HTMLDivElement, BillingSummaryProps>(
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex items-center">
|
||||
<div className="w-8 h-8 bg-blue-100 rounded-lg flex items-center justify-center">
|
||||
<CreditCardIcon className="w-5 h-5 text-blue-600" />
|
||||
<div className="w-8 h-8 bg-primary/10 rounded-lg flex items-center justify-center">
|
||||
<CreditCardIcon className="w-5 h-5 text-primary" />
|
||||
</div>
|
||||
<h3 className="ml-3 text-lg font-semibold text-gray-900">Billing Summary</h3>
|
||||
<h3 className="ml-3 text-lg font-semibold text-foreground">Billing Summary</h3>
|
||||
</div>
|
||||
{!compact && (
|
||||
<Link
|
||||
href="/billing/invoices"
|
||||
className="inline-flex items-center text-sm text-blue-600 hover:text-blue-700 font-medium"
|
||||
className="inline-flex items-center text-sm text-primary hover:text-primary-hover font-medium"
|
||||
>
|
||||
View All
|
||||
<ArrowRightIcon className="ml-1 w-4 h-4" />
|
||||
@ -108,33 +108,33 @@ const BillingSummary = forwardRef<HTMLDivElement, BillingSummaryProps>(
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-center justify-between p-3 rounded-lg bg-gray-50"
|
||||
className="flex items-center justify-between p-3 rounded-lg bg-muted/50"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<IconComponent
|
||||
className={cn(
|
||||
"w-5 h-5 mr-3",
|
||||
item.variant === "error" && "text-red-500",
|
||||
item.variant === "warning" && "text-yellow-500",
|
||||
item.variant === "success" && "text-green-500",
|
||||
item.variant === "neutral" && "text-gray-500"
|
||||
item.variant === "error" && "text-destructive",
|
||||
item.variant === "warning" && "text-warning",
|
||||
item.variant === "success" && "text-success",
|
||||
item.variant === "neutral" && "text-muted-foreground"
|
||||
)}
|
||||
/>
|
||||
<div>
|
||||
<div className="text-sm font-medium text-gray-900">{item.label}</div>
|
||||
<div className="text-sm font-medium text-foreground">{item.label}</div>
|
||||
{!compact && item.count > 0 && (
|
||||
<div className="text-xs text-gray-500">
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{item.count} invoice{item.count !== 1 ? "s" : ""}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="text-lg font-semibold text-gray-900">
|
||||
<div className="text-lg font-semibold text-foreground">
|
||||
{formatAmount(item.amount)}
|
||||
</div>
|
||||
{compact && item.count > 0 && (
|
||||
<div className="text-xs text-gray-500">
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{item.count} invoice{item.count !== 1 ? "s" : ""}
|
||||
</div>
|
||||
)}
|
||||
@ -146,20 +146,20 @@ const BillingSummary = forwardRef<HTMLDivElement, BillingSummaryProps>(
|
||||
|
||||
{/* Total Invoices */}
|
||||
{!compact && (
|
||||
<div className="mt-6 pt-4 border-t border-gray-200">
|
||||
<div className="mt-6 pt-4 border-t border-border">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-gray-600">Total Invoices</span>
|
||||
<span className="font-medium text-gray-900">{summary.invoiceCount.total}</span>
|
||||
<span className="text-muted-foreground">Total Invoices</span>
|
||||
<span className="font-medium text-foreground">{summary.invoiceCount.total}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Quick Actions */}
|
||||
{compact && (
|
||||
<div className="mt-4 pt-4 border-t border-gray-200">
|
||||
<div className="mt-4 pt-4 border-t border-border">
|
||||
<Link
|
||||
href="/billing/invoices"
|
||||
className="inline-flex items-center text-sm text-blue-600 hover:text-blue-700 font-medium"
|
||||
className="inline-flex items-center text-sm text-primary hover:text-primary-hover font-medium"
|
||||
>
|
||||
View All Invoices
|
||||
<ArrowRightIcon className="ml-1 w-4 h-4" />
|
||||
|
||||
@ -12,55 +12,65 @@ import type { InvoiceStatus } from "@customer-portal/domain/billing";
|
||||
type StatusConfig = {
|
||||
icon: ReactElement;
|
||||
color: string;
|
||||
iconColor: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
const STATUS_CONFIG: Record<InvoiceStatus, StatusConfig> = {
|
||||
Draft: {
|
||||
icon: <DocumentTextIcon className="h-5 w-5 text-gray-500" />,
|
||||
color: "bg-gray-100 text-gray-800 border-gray-200",
|
||||
icon: <DocumentTextIcon className="h-4 w-4" />,
|
||||
iconColor: "text-neutral",
|
||||
color: "bg-neutral-soft text-neutral border-neutral-border",
|
||||
label: "Draft",
|
||||
},
|
||||
Pending: {
|
||||
icon: <ClockIcon className="h-5 w-5 text-blue-500" />,
|
||||
color: "bg-blue-100 text-blue-800 border-blue-200",
|
||||
icon: <ClockIcon className="h-4 w-4" />,
|
||||
iconColor: "text-status-pending",
|
||||
color: "bg-status-pending-bg text-status-pending border-status-pending-border",
|
||||
label: "Pending",
|
||||
},
|
||||
Paid: {
|
||||
icon: <CheckCircleIcon className="h-5 w-5 text-green-500" />,
|
||||
color: "bg-green-100 text-green-800 border-green-200",
|
||||
icon: <CheckCircleIcon className="h-4 w-4" />,
|
||||
iconColor: "text-status-paid",
|
||||
color: "bg-status-paid-bg text-status-paid border-status-paid-border",
|
||||
label: "Paid",
|
||||
},
|
||||
Unpaid: {
|
||||
icon: <ClockIcon className="h-5 w-5 text-yellow-500" />,
|
||||
color: "bg-yellow-100 text-yellow-800 border-yellow-200",
|
||||
icon: <ClockIcon className="h-4 w-4" />,
|
||||
iconColor: "text-status-unpaid",
|
||||
color: "bg-status-unpaid-bg text-status-unpaid border-status-unpaid-border",
|
||||
label: "Unpaid",
|
||||
},
|
||||
Overdue: {
|
||||
icon: <ExclamationTriangleIcon className="h-5 w-5 text-red-500" />,
|
||||
color: "bg-red-100 text-red-800 border-red-200",
|
||||
icon: <ExclamationTriangleIcon className="h-4 w-4" />,
|
||||
iconColor: "text-status-overdue",
|
||||
color: "bg-status-overdue-bg text-status-overdue border-status-overdue-border",
|
||||
label: "Overdue",
|
||||
},
|
||||
Cancelled: {
|
||||
icon: <DocumentTextIcon className="h-5 w-5 text-gray-500" />,
|
||||
color: "bg-gray-100 text-gray-800 border-gray-200",
|
||||
icon: <DocumentTextIcon className="h-4 w-4" />,
|
||||
iconColor: "text-neutral",
|
||||
color: "bg-neutral-soft text-neutral border-neutral-border",
|
||||
label: "Cancelled",
|
||||
},
|
||||
Refunded: {
|
||||
icon: <CheckCircleIcon className="h-5 w-5 text-emerald-500" />,
|
||||
color: "bg-emerald-100 text-emerald-800 border-emerald-200",
|
||||
icon: <CheckCircleIcon className="h-4 w-4" />,
|
||||
iconColor: "text-info",
|
||||
color: "bg-info-soft text-info border-info-border",
|
||||
label: "Refunded",
|
||||
},
|
||||
Collections: {
|
||||
icon: <ExclamationTriangleIcon className="h-5 w-5 text-red-600" />,
|
||||
color: "bg-red-100 text-red-900 border-red-200",
|
||||
icon: <ExclamationTriangleIcon className="h-4 w-4" />,
|
||||
iconColor: "text-destructive",
|
||||
color: "bg-destructive-soft text-destructive border-destructive-border",
|
||||
label: "Collections",
|
||||
},
|
||||
};
|
||||
|
||||
const DEFAULT_STATUS: StatusConfig = {
|
||||
icon: <DocumentTextIcon className="h-5 w-5 text-gray-500" />,
|
||||
color: "bg-gray-100 text-gray-800 border-gray-200",
|
||||
icon: <DocumentTextIcon className="h-4 w-4" />,
|
||||
iconColor: "text-neutral",
|
||||
color: "bg-neutral-soft text-neutral border-neutral-border",
|
||||
label: "Unknown",
|
||||
};
|
||||
|
||||
@ -71,8 +81,8 @@ export function InvoiceStatusBadge({ status }: { status: InvoiceStatus }) {
|
||||
<span
|
||||
className={`inline-flex items-center px-2.5 py-1 text-xs font-medium rounded-full border ${config.color}`}
|
||||
>
|
||||
{config.icon}
|
||||
<span className="ml-1">{config.label}</span>
|
||||
<span className={config.iconColor}>{config.icon}</span>
|
||||
<span className="ml-1.5">{config.label}</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
@ -36,16 +36,16 @@ const getPaymentMethodIcon = (type: PaymentMethod["type"]) => {
|
||||
const getCardBrandColor = (cardBrand?: string) => {
|
||||
switch (cardBrand?.toLowerCase()) {
|
||||
case "visa":
|
||||
return "text-blue-600";
|
||||
return "text-info";
|
||||
case "mastercard":
|
||||
return "text-red-600";
|
||||
return "text-destructive";
|
||||
case "amex":
|
||||
case "american express":
|
||||
return "text-green-600";
|
||||
return "text-success";
|
||||
case "discover":
|
||||
return "text-orange-600";
|
||||
return "text-warning";
|
||||
default:
|
||||
return "text-gray-600";
|
||||
return "text-muted-foreground";
|
||||
}
|
||||
};
|
||||
|
||||
@ -78,8 +78,8 @@ const PaymentMethodCard = forwardRef<HTMLDivElement, PaymentMethodCardProps>(
|
||||
if (type === "BankAccount" || type === "RemoteBankAccount") {
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
<div className="font-medium text-gray-900">{bankName || "Bank Account"}</div>
|
||||
<div className="text-sm text-gray-600">
|
||||
<div className="font-medium text-foreground">{bankName || "Bank Account"}</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{cardLastFour && <span> •••• {cardLastFour}</span>}
|
||||
</div>
|
||||
</div>
|
||||
@ -89,11 +89,11 @@ const PaymentMethodCard = forwardRef<HTMLDivElement, PaymentMethodCardProps>(
|
||||
// Credit Card
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
<div className="font-medium text-gray-900">
|
||||
<div className="font-medium text-foreground">
|
||||
{cardType || "Credit Card"}
|
||||
{cardLastFour && <span className="ml-2">•••• {cardLastFour}</span>}
|
||||
</div>
|
||||
<div className="text-sm text-gray-600">
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{formatExpiryDate(expiryDate) && <span>Expires {formatExpiryDate(expiryDate)}</span>}
|
||||
{gatewayName && <span className="ml-2">• {gatewayName}</span>}
|
||||
</div>
|
||||
@ -105,9 +105,9 @@ const PaymentMethodCard = forwardRef<HTMLDivElement, PaymentMethodCardProps>(
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"bg-white border border-gray-200 rounded-lg transition-all duration-200",
|
||||
"hover:border-gray-300 hover:shadow-sm",
|
||||
isDefault && "ring-2 ring-blue-500 border-blue-200",
|
||||
"bg-card border border-border rounded-lg transition-all duration-200",
|
||||
"hover:border-border-muted hover:shadow-sm",
|
||||
isDefault && "ring-2 ring-primary border-primary/30",
|
||||
compact ? "p-3" : "p-4",
|
||||
className
|
||||
)}
|
||||
@ -119,10 +119,10 @@ const PaymentMethodCard = forwardRef<HTMLDivElement, PaymentMethodCardProps>(
|
||||
<div
|
||||
className={cn(
|
||||
"flex-shrink-0 rounded-lg flex items-center justify-center",
|
||||
compact ? "w-8 h-8 bg-gray-100" : "w-10 h-10 bg-gray-100",
|
||||
compact ? "w-8 h-8 bg-muted" : "w-10 h-10 bg-muted",
|
||||
type === "CreditCard" || type === "RemoteCreditCard"
|
||||
? getCardBrandColor(cardType)
|
||||
: "text-gray-600"
|
||||
: "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
{getPaymentMethodIcon(type)}
|
||||
@ -134,7 +134,7 @@ const PaymentMethodCard = forwardRef<HTMLDivElement, PaymentMethodCardProps>(
|
||||
|
||||
{/* Description if different from generated details */}
|
||||
{description && !description.includes(cardLastFour || "") && (
|
||||
<div className="text-sm text-gray-500 mt-1 truncate">{description}</div>
|
||||
<div className="text-sm text-muted-foreground mt-1 truncate">{description}</div>
|
||||
)}
|
||||
|
||||
{/* Default badge */}
|
||||
@ -153,7 +153,7 @@ const PaymentMethodCard = forwardRef<HTMLDivElement, PaymentMethodCardProps>(
|
||||
<div className="flex-shrink-0 ml-2">
|
||||
<button
|
||||
type="button"
|
||||
className="p-1 text-gray-400 hover:text-gray-600 rounded-md hover:bg-gray-100"
|
||||
className="p-1 text-muted-foreground hover:text-foreground rounded-md hover:bg-muted"
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
// Payment method actions (edit, delete) are handled via dedicated modal flows
|
||||
@ -168,7 +168,7 @@ const PaymentMethodCard = forwardRef<HTMLDivElement, PaymentMethodCardProps>(
|
||||
|
||||
{/* Gateway info for compact view */}
|
||||
{compact && gatewayName && (
|
||||
<div className="mt-2 text-xs text-gray-500">via {gatewayName}</div>
|
||||
<div className="mt-2 text-xs text-muted-foreground">via {gatewayName}</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -19,19 +19,19 @@ export function CardBadge({ text, variant = "default", size = "md" }: CardBadgeP
|
||||
const getVariantClasses = () => {
|
||||
switch (variant) {
|
||||
case "gold":
|
||||
return "bg-yellow-50 text-yellow-700 border-yellow-200";
|
||||
return "bg-warning-bg text-warning border-warning-border";
|
||||
case "platinum":
|
||||
return "bg-indigo-50 text-indigo-700 border-indigo-200";
|
||||
return "bg-status-completed-bg text-status-completed border-status-completed-border";
|
||||
case "silver":
|
||||
return "bg-gray-50 text-gray-700 border-gray-200";
|
||||
return "bg-neutral-soft text-neutral border-neutral-border";
|
||||
case "recommended":
|
||||
return "bg-green-50 text-green-700 border-green-200";
|
||||
return "bg-success-bg text-success border-success-border";
|
||||
case "family":
|
||||
return "bg-blue-50 text-blue-700 border-blue-200";
|
||||
return "bg-info-soft text-info border-info-border";
|
||||
case "new":
|
||||
return "bg-purple-50 text-purple-700 border-purple-200";
|
||||
return "bg-primary/10 text-primary border-primary/20";
|
||||
default:
|
||||
return "bg-gray-50 text-gray-700 border-gray-200";
|
||||
return "bg-neutral-soft text-neutral border-neutral-border";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -38,29 +38,29 @@ export function DataUsageChart({
|
||||
};
|
||||
|
||||
const getUsageColor = (percentage: number) => {
|
||||
if (percentage >= 90) return "bg-red-500";
|
||||
if (percentage >= 75) return "bg-yellow-500";
|
||||
if (percentage >= 50) return "bg-orange-500";
|
||||
return "bg-green-500";
|
||||
if (percentage >= 90) return "bg-destructive";
|
||||
if (percentage >= 75) return "bg-warning";
|
||||
if (percentage >= 50) return "bg-status-unpaid";
|
||||
return "bg-success";
|
||||
};
|
||||
|
||||
const getUsageTextColor = (percentage: number) => {
|
||||
if (percentage >= 90) return "text-red-600";
|
||||
if (percentage >= 75) return "text-yellow-600";
|
||||
if (percentage >= 50) return "text-orange-600";
|
||||
return "text-green-600";
|
||||
if (percentage >= 90) return "text-destructive";
|
||||
if (percentage >= 75) return "text-warning";
|
||||
if (percentage >= 50) return "text-status-unpaid";
|
||||
return "text-success";
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className={`${embedded ? "" : "bg-white shadow rounded-lg "}p-6`}>
|
||||
<div className={`${embedded ? "" : "bg-card shadow rounded-lg "}p-6`}>
|
||||
<div className="animate-pulse">
|
||||
<div className="h-6 bg-gray-200 rounded w-1/3 mb-4"></div>
|
||||
<div className="h-4 bg-gray-200 rounded w-full mb-2"></div>
|
||||
<div className="h-8 bg-gray-200 rounded mb-4"></div>
|
||||
<div className="h-6 bg-muted rounded w-1/3 mb-4"></div>
|
||||
<div className="h-4 bg-muted rounded w-full mb-2"></div>
|
||||
<div className="h-8 bg-muted rounded mb-4"></div>
|
||||
<div className="space-y-2">
|
||||
<div className="h-4 bg-gray-200 rounded w-3/4"></div>
|
||||
<div className="h-4 bg-gray-200 rounded w-1/2"></div>
|
||||
<div className="h-4 bg-muted rounded w-3/4"></div>
|
||||
<div className="h-4 bg-muted rounded w-1/2"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -69,11 +69,11 @@ export function DataUsageChart({
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className={`${embedded ? "" : "bg-white shadow rounded-lg "}p-6`}>
|
||||
<div className={`${embedded ? "" : "bg-card shadow rounded-lg "}p-6`}>
|
||||
<div className="text-center">
|
||||
<ExclamationTriangleIcon className="h-12 w-12 text-red-400 mx-auto mb-4" />
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-2">Error Loading Usage Data</h3>
|
||||
<p className="text-red-600">{error}</p>
|
||||
<ExclamationTriangleIcon className="h-12 w-12 text-destructive mx-auto mb-4" />
|
||||
<h3 className="text-lg font-medium text-foreground mb-2">Error Loading Usage Data</h3>
|
||||
<p className="text-destructive">{error}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -87,17 +87,17 @@ export function DataUsageChart({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${embedded ? "" : "bg-white shadow-lg rounded-xl border border-gray-100 hover:shadow-xl transition-shadow duration-300"}`}
|
||||
className={`${embedded ? "" : "bg-card shadow-lg rounded-xl border border-border hover:shadow-xl transition-shadow duration-300"}`}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className={`${embedded ? "" : "px-6 lg:px-8 py-5 border-b border-gray-200"}`}>
|
||||
<div className={`${embedded ? "" : "px-6 lg:px-8 py-5 border-b border-border"}`}>
|
||||
<div className="flex items-center">
|
||||
<div className="bg-blue-50 rounded-xl p-2 mr-4">
|
||||
<ChartBarIcon className="h-6 w-6 text-blue-600" />
|
||||
<div className="bg-primary/10 rounded-xl p-2 mr-4">
|
||||
<ChartBarIcon className="h-6 w-6 text-primary" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold text-gray-900">Data Usage</h3>
|
||||
<p className="text-sm text-gray-600">Current month usage and remaining quota</p>
|
||||
<h3 className="text-xl font-semibold text-foreground">Data Usage</h3>
|
||||
<p className="text-sm text-muted-foreground">Current month usage and remaining quota</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -107,21 +107,21 @@ export function DataUsageChart({
|
||||
{/* Current Usage Overview */}
|
||||
<div className="mb-6">
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<span className="text-sm font-medium text-gray-700">Used this month</span>
|
||||
<span className="text-sm font-medium text-foreground">Used this month</span>
|
||||
<span className={`text-sm font-semibold ${getUsageTextColor(usagePercentage)}`}>
|
||||
{formatUsage(totalRecentUsage)} of {formatUsage(totalQuota)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Progress Bar */}
|
||||
<div className="w-full bg-gray-200 rounded-full h-3">
|
||||
<div className="w-full bg-muted rounded-full h-3">
|
||||
<div
|
||||
className={`h-3 rounded-full transition-all duration-300 ${getUsageColor(usagePercentage)}`}
|
||||
style={{ width: `${Math.min(usagePercentage, 100)}%` }}
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between text-xs text-gray-500 mt-1">
|
||||
<div className="flex justify-between text-xs text-muted-foreground mt-1">
|
||||
<span>0%</span>
|
||||
<span className={getUsageTextColor(usagePercentage)}>
|
||||
{usagePercentage.toFixed(1)}% used
|
||||
@ -132,17 +132,17 @@ export function DataUsageChart({
|
||||
|
||||
{/* Today's Usage */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-8">
|
||||
<div className="bg-gradient-to-br from-blue-50 to-blue-100 rounded-xl p-6 border border-blue-200">
|
||||
<div className="bg-info-bg rounded-xl p-6 border border-info-border">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="text-3xl font-bold text-blue-600">
|
||||
<div className="text-3xl font-bold text-info">
|
||||
{formatUsage(usage.todayUsageMb)}
|
||||
</div>
|
||||
<div className="text-sm font-medium text-blue-700 mt-1">Used today</div>
|
||||
<div className="text-sm font-medium text-info mt-1">Used today</div>
|
||||
</div>
|
||||
<div className="bg-blue-200 rounded-full p-3">
|
||||
<div className="bg-info/20 rounded-full p-3">
|
||||
<svg
|
||||
className="h-6 w-6 text-blue-600"
|
||||
className="h-6 w-6 text-info"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
@ -158,17 +158,17 @@ export function DataUsageChart({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-gradient-to-br from-green-50 to-green-100 rounded-xl p-6 border border-green-200">
|
||||
<div className="bg-success-bg rounded-xl p-6 border border-success-border">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="text-3xl font-bold text-green-600">
|
||||
<div className="text-3xl font-bold text-success">
|
||||
{formatUsage(remainingQuotaMb)}
|
||||
</div>
|
||||
<div className="text-sm font-medium text-green-700 mt-1">Remaining</div>
|
||||
<div className="text-sm font-medium text-success mt-1">Remaining</div>
|
||||
</div>
|
||||
<div className="bg-green-200 rounded-full p-3">
|
||||
<div className="bg-success/20 rounded-full p-3">
|
||||
<svg
|
||||
className="h-6 w-6 text-green-600"
|
||||
className="h-6 w-6 text-success"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
@ -188,7 +188,7 @@ export function DataUsageChart({
|
||||
{/* Recent Days Usage */}
|
||||
{usage.recentDaysUsage.length > 0 && (
|
||||
<div>
|
||||
<h4 className="text-sm font-medium text-gray-500 uppercase tracking-wider mb-3">
|
||||
<h4 className="text-sm font-medium text-muted-foreground uppercase tracking-wider mb-3">
|
||||
Recent Usage History
|
||||
</h4>
|
||||
<div className="space-y-2">
|
||||
@ -196,20 +196,20 @@ export function DataUsageChart({
|
||||
const dayPercentage = totalQuota > 0 ? (day.usageMb / totalQuota) * 100 : 0;
|
||||
return (
|
||||
<div key={index} className="flex items-center justify-between py-2">
|
||||
<span className="text-sm text-gray-600">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{new Date(day.date).toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
})}
|
||||
</span>
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="w-24 bg-gray-200 rounded-full h-2">
|
||||
<div className="w-24 bg-muted rounded-full h-2">
|
||||
<div
|
||||
className="bg-blue-500 h-2 rounded-full transition-all duration-300"
|
||||
className="bg-primary h-2 rounded-full transition-all duration-300"
|
||||
style={{ width: `${Math.min(dayPercentage, 100)}%` }}
|
||||
></div>
|
||||
</div>
|
||||
<span className="text-sm font-medium text-gray-900 w-16 text-right">
|
||||
<span className="text-sm font-medium text-foreground w-16 text-right">
|
||||
{formatUsage(day.usageMb)}
|
||||
</span>
|
||||
</div>
|
||||
@ -223,12 +223,12 @@ export function DataUsageChart({
|
||||
{/* Warnings */}
|
||||
|
||||
{usagePercentage >= 90 && (
|
||||
<div className="mt-6 bg-red-50 border border-red-200 rounded-lg p-4">
|
||||
<div className="mt-6 bg-destructive-bg border border-destructive-border rounded-lg p-4">
|
||||
<div className="flex items-center">
|
||||
<ExclamationTriangleIcon className="h-5 w-5 text-red-500 mr-2" />
|
||||
<ExclamationTriangleIcon className="h-5 w-5 text-destructive mr-2" />
|
||||
<div>
|
||||
<h4 className="text-sm font-medium text-red-800">High Usage Warning</h4>
|
||||
<p className="text-sm text-red-700 mt-1">
|
||||
<h4 className="text-sm font-medium text-destructive">High Usage Warning</h4>
|
||||
<p className="text-sm text-destructive/80 mt-1">
|
||||
You have used {usagePercentage.toFixed(1)}% of your data quota. Consider topping
|
||||
up to avoid service interruption.
|
||||
</p>
|
||||
@ -238,12 +238,12 @@ export function DataUsageChart({
|
||||
)}
|
||||
|
||||
{usagePercentage >= 75 && usagePercentage < 90 && (
|
||||
<div className="mt-6 bg-yellow-50 border border-yellow-200 rounded-lg p-4">
|
||||
<div className="mt-6 bg-warning-bg border border-warning-border rounded-lg p-4">
|
||||
<div className="flex items-center">
|
||||
<ExclamationTriangleIcon className="h-5 w-5 text-yellow-500 mr-2" />
|
||||
<ExclamationTriangleIcon className="h-5 w-5 text-warning mr-2" />
|
||||
<div>
|
||||
<h4 className="text-sm font-medium text-yellow-800">Usage Notice</h4>
|
||||
<p className="text-sm text-yellow-700 mt-1">
|
||||
<h4 className="text-sm font-medium text-warning">Usage Notice</h4>
|
||||
<p className="text-sm text-warning/80 mt-1">
|
||||
You have used {usagePercentage.toFixed(1)}% of your data quota. Consider
|
||||
monitoring your usage.
|
||||
</p>
|
||||
|
||||
@ -62,8 +62,8 @@ export const SubscriptionDetails = forwardRef<HTMLDivElement, SubscriptionDetail
|
||||
<div className="flex items-center space-x-3">
|
||||
{getSubscriptionStatusIcon(subscription.status)}
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-900">Subscription Details</h3>
|
||||
<p className="text-sm text-gray-500">Service subscription information</p>
|
||||
<h3 className="text-lg font-semibold text-foreground">Subscription Details</h3>
|
||||
<p className="text-sm text-muted-foreground">Service subscription information</p>
|
||||
</div>
|
||||
</div>
|
||||
<StatusPill
|
||||
@ -78,78 +78,82 @@ export const SubscriptionDetails = forwardRef<HTMLDivElement, SubscriptionDetail
|
||||
{/* Billing Amount */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<CurrencyYenIcon className="h-5 w-5 text-gray-400" />
|
||||
<h4 className="text-sm font-medium text-gray-500 uppercase tracking-wider">
|
||||
<CurrencyYenIcon className="h-5 w-5 text-muted-foreground" />
|
||||
<h4 className="text-sm font-medium text-muted-foreground uppercase tracking-wider">
|
||||
Billing Amount
|
||||
</h4>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-gray-900">
|
||||
<p className="text-2xl font-bold text-foreground">
|
||||
{formatCurrency(subscription.amount)}
|
||||
</p>
|
||||
<p className="text-sm text-gray-500">{getBillingCycleLabel(subscription.cycle)}</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{getBillingCycleLabel(subscription.cycle)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Next Due Date */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<CalendarIcon className="h-5 w-5 text-gray-400" />
|
||||
<h4 className="text-sm font-medium text-gray-500 uppercase tracking-wider">
|
||||
<CalendarIcon className="h-5 w-5 text-muted-foreground" />
|
||||
<h4 className="text-sm font-medium text-muted-foreground uppercase tracking-wider">
|
||||
Next Due Date
|
||||
</h4>
|
||||
</div>
|
||||
<p className="text-lg font-semibold text-gray-900">
|
||||
<p className="text-lg font-semibold text-foreground">
|
||||
{formatDate(subscription.nextDue)}
|
||||
</p>
|
||||
<p className="text-sm text-gray-500">Due date</p>
|
||||
<p className="text-sm text-muted-foreground">Due date</p>
|
||||
</div>
|
||||
|
||||
{/* Registration Date */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<TagIcon className="h-5 w-5 text-gray-400" />
|
||||
<h4 className="text-sm font-medium text-gray-500 uppercase tracking-wider">
|
||||
<TagIcon className="h-5 w-5 text-muted-foreground" />
|
||||
<h4 className="text-sm font-medium text-muted-foreground uppercase tracking-wider">
|
||||
Registration Date
|
||||
</h4>
|
||||
</div>
|
||||
<p className="text-lg font-semibold text-gray-900">
|
||||
<p className="text-lg font-semibold text-foreground">
|
||||
{formatDate(subscription.registrationDate)}
|
||||
</p>
|
||||
<p className="text-sm text-gray-500">Service created</p>
|
||||
<p className="text-sm text-muted-foreground">Service created</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Additional Details */}
|
||||
<div className="mt-6 pt-6 border-t border-gray-100">
|
||||
<div className="mt-6 pt-6 border-t border-border">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<IdentificationIcon className="h-5 w-5 text-gray-400" />
|
||||
<h4 className="text-sm font-medium text-gray-500">Service ID</h4>
|
||||
<IdentificationIcon className="h-5 w-5 text-muted-foreground" />
|
||||
<h4 className="text-sm font-medium text-muted-foreground">Service ID</h4>
|
||||
</div>
|
||||
<p className="text-base font-medium text-gray-900">{subscription.serviceId}</p>
|
||||
<p className="text-base font-medium text-foreground">{subscription.serviceId}</p>
|
||||
</div>
|
||||
|
||||
{subscription.orderNumber && (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<TagIcon className="h-5 w-5 text-gray-400" />
|
||||
<h4 className="text-sm font-medium text-gray-500">Order Number</h4>
|
||||
<TagIcon className="h-5 w-5 text-muted-foreground" />
|
||||
<h4 className="text-sm font-medium text-muted-foreground">Order Number</h4>
|
||||
</div>
|
||||
<p className="text-base font-medium text-gray-900">{subscription.orderNumber}</p>
|
||||
<p className="text-base font-medium text-foreground">
|
||||
{subscription.orderNumber}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{subscription.groupName && (
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-sm font-medium text-gray-500">Product Group</h4>
|
||||
<p className="text-base font-medium text-gray-900">{subscription.groupName}</p>
|
||||
<h4 className="text-sm font-medium text-muted-foreground">Product Group</h4>
|
||||
<p className="text-base font-medium text-foreground">{subscription.groupName}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{subscription.paymentMethod && (
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-sm font-medium text-gray-500">Payment Method</h4>
|
||||
<p className="text-base font-medium text-gray-900">
|
||||
<h4 className="text-sm font-medium text-muted-foreground">Payment Method</h4>
|
||||
<p className="text-base font-medium text-foreground">
|
||||
{subscription.paymentMethod}
|
||||
</p>
|
||||
</div>
|
||||
@ -159,15 +163,17 @@ export const SubscriptionDetails = forwardRef<HTMLDivElement, SubscriptionDetail
|
||||
|
||||
{/* Custom Fields */}
|
||||
{subscription.customFields && Object.keys(subscription.customFields).length > 0 && (
|
||||
<div className="mt-6 pt-6 border-t border-gray-100">
|
||||
<h4 className="text-sm font-medium text-gray-500 mb-3">Additional Information</h4>
|
||||
<div className="mt-6 pt-6 border-t border-border">
|
||||
<h4 className="text-sm font-medium text-muted-foreground mb-3">
|
||||
Additional Information
|
||||
</h4>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{Object.entries(subscription.customFields).map(([key, value]) => (
|
||||
<div key={key} className="space-y-1">
|
||||
<p className="text-sm text-gray-500 capitalize">
|
||||
<p className="text-sm text-muted-foreground capitalize">
|
||||
{key.replace(/([A-Z])/g, " $1").trim()}
|
||||
</p>
|
||||
<p className="text-sm font-medium text-gray-900">{value}</p>
|
||||
<p className="text-sm font-medium text-foreground">{value}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@ -176,9 +182,9 @@ export const SubscriptionDetails = forwardRef<HTMLDivElement, SubscriptionDetail
|
||||
|
||||
{/* Notes */}
|
||||
{subscription.notes && (
|
||||
<div className="mt-6 pt-6 border-t border-gray-100">
|
||||
<h4 className="text-sm font-medium text-gray-500 mb-2">Notes</h4>
|
||||
<p className="text-sm text-gray-700 bg-gray-50 rounded-md p-3">
|
||||
<div className="mt-6 pt-6 border-t border-border">
|
||||
<h4 className="text-sm font-medium text-muted-foreground mb-2">Notes</h4>
|
||||
<p className="text-sm text-foreground bg-muted rounded-md p-3">
|
||||
{subscription.notes}
|
||||
</p>
|
||||
</div>
|
||||
@ -191,20 +197,20 @@ export const SubscriptionDetails = forwardRef<HTMLDivElement, SubscriptionDetail
|
||||
{/* SIM Service Information */}
|
||||
{isSimService(subscription.productName) && (
|
||||
<SubCard title="SIM Service Information" icon={<ServerIcon className="h-5 w-5" />}>
|
||||
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
||||
<div className="bg-info-bg border border-info-border rounded-lg p-4">
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="bg-blue-100 rounded-lg p-2">
|
||||
<ServerIcon className="h-5 w-5 text-blue-600" />
|
||||
<div className="bg-info/20 rounded-lg p-2">
|
||||
<ServerIcon className="h-5 w-5 text-info" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-sm font-semibold text-blue-900 mb-2">
|
||||
<h4 className="text-sm font-semibold text-info mb-2">
|
||||
SIM Management Available
|
||||
</h4>
|
||||
<p className="text-sm text-blue-800 mb-3">
|
||||
<p className="text-sm text-info/80 mb-3">
|
||||
This subscription includes SIM management features such as data usage
|
||||
monitoring, top-up options, and service configuration.
|
||||
</p>
|
||||
<ul className="text-sm text-blue-700 space-y-1">
|
||||
<ul className="text-sm text-info/70 space-y-1">
|
||||
<li>• Real-time data usage tracking</li>
|
||||
<li>• Data top-up and plan changes</li>
|
||||
<li>• Service feature toggles</li>
|
||||
@ -222,19 +228,19 @@ export const SubscriptionDetails = forwardRef<HTMLDivElement, SubscriptionDetail
|
||||
title="Internet Service Information"
|
||||
icon={<ServerIcon className="h-5 w-5" />}
|
||||
>
|
||||
<div className="bg-green-50 border border-green-200 rounded-lg p-4">
|
||||
<div className="bg-success-bg border border-success-border rounded-lg p-4">
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="bg-green-100 rounded-lg p-2">
|
||||
<ServerIcon className="h-5 w-5 text-green-600" />
|
||||
<div className="bg-success/20 rounded-lg p-2">
|
||||
<ServerIcon className="h-5 w-5 text-success" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-sm font-semibold text-green-900 mb-2">
|
||||
<h4 className="text-sm font-semibold text-success mb-2">
|
||||
Internet Service Features
|
||||
</h4>
|
||||
<p className="text-sm text-green-800 mb-3">
|
||||
<p className="text-sm text-success/80 mb-3">
|
||||
Your internet service includes high-speed connectivity and support options.
|
||||
</p>
|
||||
<ul className="text-sm text-green-700 space-y-1">
|
||||
<ul className="text-sm text-success/70 space-y-1">
|
||||
<li>• High-speed internet connection</li>
|
||||
<li>• 24/7 technical support</li>
|
||||
<li>• Service monitoring</li>
|
||||
@ -249,19 +255,19 @@ export const SubscriptionDetails = forwardRef<HTMLDivElement, SubscriptionDetail
|
||||
{/* VPN Service Information */}
|
||||
{isVpnService(subscription.productName) && (
|
||||
<SubCard title="VPN Service Information" icon={<ServerIcon className="h-5 w-5" />}>
|
||||
<div className="bg-purple-50 border border-purple-200 rounded-lg p-4">
|
||||
<div className="bg-status-completed-bg border border-status-completed-border rounded-lg p-4">
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="bg-purple-100 rounded-lg p-2">
|
||||
<ServerIcon className="h-5 w-5 text-purple-600" />
|
||||
<div className="bg-status-completed/20 rounded-lg p-2">
|
||||
<ServerIcon className="h-5 w-5 text-status-completed" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-sm font-semibold text-purple-900 mb-2">
|
||||
<h4 className="text-sm font-semibold text-status-completed mb-2">
|
||||
VPN Service Features
|
||||
</h4>
|
||||
<p className="text-sm text-purple-800 mb-3">
|
||||
<p className="text-sm text-status-completed/80 mb-3">
|
||||
Your VPN service provides secure and private internet access.
|
||||
</p>
|
||||
<ul className="text-sm text-purple-700 space-y-1">
|
||||
<ul className="text-sm text-status-completed/70 space-y-1">
|
||||
<li>• Encrypted internet connection</li>
|
||||
<li>• Multiple server locations</li>
|
||||
<li>• No-logs policy</li>
|
||||
|
||||
@ -6,20 +6,25 @@ import {
|
||||
} from "@customer-portal/domain/subscriptions";
|
||||
|
||||
const STATUS_COLOR_MAP: Record<SubscriptionStatus, string> = {
|
||||
[SUBSCRIPTION_STATUS.ACTIVE]: "bg-green-100 text-green-800",
|
||||
[SUBSCRIPTION_STATUS.INACTIVE]: "bg-gray-100 text-gray-800",
|
||||
[SUBSCRIPTION_STATUS.PENDING]: "bg-blue-100 text-blue-800",
|
||||
[SUBSCRIPTION_STATUS.SUSPENDED]: "bg-yellow-100 text-yellow-800",
|
||||
[SUBSCRIPTION_STATUS.CANCELLED]: "bg-gray-100 text-gray-800",
|
||||
[SUBSCRIPTION_STATUS.TERMINATED]: "bg-red-100 text-red-800",
|
||||
[SUBSCRIPTION_STATUS.COMPLETED]: "bg-green-100 text-green-800",
|
||||
[SUBSCRIPTION_STATUS.ACTIVE]:
|
||||
"bg-status-active-bg text-status-active border-status-active-border",
|
||||
[SUBSCRIPTION_STATUS.INACTIVE]: "bg-neutral-soft text-neutral border-neutral-border",
|
||||
[SUBSCRIPTION_STATUS.PENDING]:
|
||||
"bg-status-pending-bg text-status-pending border-status-pending-border",
|
||||
[SUBSCRIPTION_STATUS.SUSPENDED]:
|
||||
"bg-status-suspended-bg text-status-suspended border-status-suspended-border",
|
||||
[SUBSCRIPTION_STATUS.CANCELLED]: "bg-neutral-soft text-neutral border-neutral-border",
|
||||
[SUBSCRIPTION_STATUS.TERMINATED]:
|
||||
"bg-status-terminated-bg text-status-terminated border-status-terminated-border",
|
||||
[SUBSCRIPTION_STATUS.COMPLETED]:
|
||||
"bg-status-completed-bg text-status-completed border-status-completed-border",
|
||||
};
|
||||
|
||||
export function SubscriptionStatusBadge({ status }: { status: SubscriptionStatus }) {
|
||||
const color = STATUS_COLOR_MAP[status] ?? "bg-gray-100 text-gray-800";
|
||||
const color = STATUS_COLOR_MAP[status] ?? "bg-neutral-soft text-neutral border-neutral-border";
|
||||
|
||||
return (
|
||||
<span className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${color}`}>
|
||||
<span className={`inline-flex px-2.5 py-1 text-xs font-medium rounded-full border ${color}`}>
|
||||
{status}
|
||||
</span>
|
||||
);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user