217 lines
7.0 KiB
TypeScript
Raw Normal View History

"use client";
import { forwardRef } from "react";
import { CalendarIcon, ArrowTopRightOnSquareIcon } from "@heroicons/react/24/outline";
import { StatusPill } from "@/components/atoms/status-pill";
import { Button } from "@/components/atoms/button";
import { SubCard } from "@/components/molecules/SubCard/SubCard";
import type { Subscription } from "@customer-portal/domain/subscriptions";
import { useFormatCurrency } from "@/shared/hooks";
import { cn, formatIsoDate } from "@/shared/utils";
import {
getBillingCycleLabel,
getSubscriptionStatusIcon,
getSubscriptionStatusVariant,
} from "@/features/subscriptions/utils/status-presenters";
interface SubscriptionCardProps {
subscription: Subscription;
variant?: "list" | "grid";
showActions?: boolean;
onViewClick?: (subscription: Subscription) => void;
className?: string;
}
function GridVariant({
ref,
subscription,
showActions,
formatCurrency,
onViewClick,
className,
}: {
ref: React.Ref<HTMLDivElement>;
subscription: Subscription;
showActions: boolean;
formatCurrency: ReturnType<typeof useFormatCurrency>["formatCurrency"];
onViewClick: ((subscription: Subscription) => void) | undefined;
className: string | undefined;
}) {
return (
<SubCard
ref={ref}
className={cn(
"hover:shadow-lg hover:-translate-y-0.5 transition-all duration-200",
className
)}
>
<div className="space-y-4">
<div className="flex items-start justify-between">
<div className="flex items-center space-x-3">
{getSubscriptionStatusIcon(subscription.status)}
<div className="min-w-0 flex-1">
<h3 className="text-lg font-semibold text-foreground truncate">
{subscription.productName}
</h3>
<p className="text-sm text-muted-foreground">Service ID: {subscription.serviceId}</p>
</div>
</div>
<StatusPill
label={subscription.status}
variant={getSubscriptionStatusVariant(subscription.status)}
size="sm"
/>
</div>
<div className="grid grid-cols-2 gap-4 text-sm">
<div>
<p className="text-muted-foreground text-xs font-medium uppercase tracking-wider">
Price
</p>
<p className="font-semibold text-foreground mt-1">
{formatCurrency(subscription.amount)}
</p>
<p className="text-xs text-muted-foreground">
{getBillingCycleLabel(subscription.cycle)}
</p>
</div>
<div>
<p className="text-muted-foreground text-xs font-medium uppercase tracking-wider">
Next Due
</p>
<div className="flex items-center space-x-1 mt-1">
<CalendarIcon className="h-4 w-4 text-muted-foreground/60" />
<p className="font-medium text-foreground">{formatIsoDate(subscription.nextDue)}</p>
</div>
</div>
</div>
{showActions && (
<div className="flex items-center justify-between pt-3 border-t border-border/60">
<p className="text-xs text-muted-foreground">
Created {formatIsoDate(subscription.registrationDate)}
</p>
<div className="flex items-center space-x-2">
<Button
variant="outline"
size="sm"
onClick={() => onViewClick?.(subscription)}
rightIcon={<ArrowTopRightOnSquareIcon className="h-4 w-4" />}
>
View
</Button>
</div>
</div>
)}
</div>
</SubCard>
);
}
function ListVariant({
ref,
subscription,
showActions,
formatCurrency,
onViewClick,
className,
}: {
ref: React.Ref<HTMLDivElement>;
subscription: Subscription;
showActions: boolean;
formatCurrency: ReturnType<typeof useFormatCurrency>["formatCurrency"];
onViewClick: ((subscription: Subscription) => void) | undefined;
className: string | undefined;
}) {
return (
<SubCard
ref={ref}
className={cn(
"hover:shadow-lg hover:-translate-y-0.5 transition-all duration-200",
className
)}
>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-4 min-w-0 flex-1">
{getSubscriptionStatusIcon(subscription.status)}
<div className="min-w-0 flex-1">
<div className="flex items-center space-x-3">
<h3 className="text-base font-semibold text-foreground truncate">
{subscription.productName}
</h3>
<StatusPill
label={subscription.status}
variant={getSubscriptionStatusVariant(subscription.status)}
size="sm"
/>
</div>
<p className="text-sm text-muted-foreground mt-1">
Service ID: {subscription.serviceId}
</p>
</div>
</div>
<div className="flex items-center space-x-6 text-sm">
<div className="text-right">
<p className="font-semibold text-foreground tabular-nums">
{formatCurrency(subscription.amount)}
</p>
<p className="text-muted-foreground text-xs">
{getBillingCycleLabel(subscription.cycle)}
</p>
</div>
<div className="text-right hidden sm:block">
<div className="flex items-center space-x-1">
<CalendarIcon className="h-4 w-4 text-muted-foreground/60" />
<p className="text-foreground">{formatIsoDate(subscription.nextDue)}</p>
</div>
<p className="text-muted-foreground text-xs">Next due</p>
</div>
{showActions && (
<div className="flex items-center space-x-2">
<Button
variant="ghost"
size="sm"
onClick={() => onViewClick?.(subscription)}
rightIcon={<ArrowTopRightOnSquareIcon className="h-4 w-4" />}
>
View
</Button>
</div>
)}
</div>
</div>
</SubCard>
);
}
export const SubscriptionCard = forwardRef<HTMLDivElement, SubscriptionCardProps>(
({ subscription, variant = "list", showActions = true, onViewClick, className }, ref) => {
const { formatCurrency } = useFormatCurrency();
if (variant === "grid") {
return (
<GridVariant
ref={ref}
subscription={subscription}
showActions={showActions}
formatCurrency={formatCurrency}
onViewClick={onViewClick}
className={className}
/>
);
}
return (
<ListVariant
ref={ref}
subscription={subscription}
showActions={showActions}
formatCurrency={formatCurrency}
onViewClick={onViewClick}
className={className}
/>
);
}
);
SubscriptionCard.displayName = "SubscriptionCard";