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:
barsa 2025-12-16 17:11:43 +09:00
parent 1f7f77775b
commit c13e2df55f
7 changed files with 199 additions and 178 deletions

View File

@ -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" />

View File

@ -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>
);
}

View File

@ -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>
);

View File

@ -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";
}
};

View File

@ -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>

View File

@ -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>

View File

@ -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>
);