- Updated multiple components to use consistent conditional rendering syntax by adding parentheses around conditions. - Enhanced readability and maintainability of the code in components such as OtpInput, AddressCard, and others. - Improved overall code quality and developer experience through uniformity in the codebase.
217 lines
7.0 KiB
TypeScript
217 lines
7.0 KiB
TypeScript
"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";
|