95 lines
2.9 KiB
TypeScript
95 lines
2.9 KiB
TypeScript
import React from 'react';
|
|
import type { LucideIcon } from 'lucide-react';
|
|
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
|
import { Progress } from '@/components/ui/progress';
|
|
|
|
import { FrostedCard } from './frosted-surface';
|
|
import { ChecklistRow } from './checklist-row';
|
|
|
|
export type ChecklistStep = {
|
|
key: string;
|
|
title: string;
|
|
description: string;
|
|
done: boolean;
|
|
ctaLabel?: string | null;
|
|
onAction?: () => void;
|
|
icon: LucideIcon;
|
|
};
|
|
|
|
type TenantOnboardingChecklistCardProps = {
|
|
title: string;
|
|
description?: string;
|
|
steps: ChecklistStep[];
|
|
completedLabel: string;
|
|
pendingLabel: string;
|
|
completionPercent: number;
|
|
completedCount: number;
|
|
totalCount: number;
|
|
emptyCopy?: string;
|
|
fallbackActionLabel?: string;
|
|
};
|
|
|
|
export function TenantOnboardingChecklistCard({
|
|
title,
|
|
description,
|
|
steps,
|
|
completedLabel,
|
|
pendingLabel,
|
|
completionPercent,
|
|
completedCount,
|
|
totalCount,
|
|
emptyCopy,
|
|
fallbackActionLabel,
|
|
}: TenantOnboardingChecklistCardProps) {
|
|
return (
|
|
<FrostedCard className="relative overflow-hidden">
|
|
<CardHeader className="relative flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
|
|
<div
|
|
aria-hidden
|
|
className="pointer-events-none absolute -top-12 -right-16 h-32 w-32 rounded-full bg-rose-200/40 blur-3xl"
|
|
/>
|
|
<div className="relative">
|
|
<CardTitle>{title}</CardTitle>
|
|
{description ? <CardDescription>{description}</CardDescription> : null}
|
|
</div>
|
|
<Badge className="bg-brand-rose-soft text-brand-rose">
|
|
{completionPercent}% · {completedCount}/{totalCount}
|
|
</Badge>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<Progress value={completionPercent} className="h-2 bg-rose-100 motion-safe:animate-pulse" />
|
|
<div className="space-y-3">
|
|
{steps.map((step) => {
|
|
const Icon = step.icon;
|
|
|
|
return (
|
|
<ChecklistRow
|
|
key={step.key}
|
|
icon={<Icon className="h-5 w-5" />}
|
|
label={step.title}
|
|
hint={step.description}
|
|
completed={step.done}
|
|
status={{ complete: completedLabel, pending: pendingLabel }}
|
|
action={
|
|
step.done || (!step.ctaLabel && !fallbackActionLabel)
|
|
? undefined
|
|
: {
|
|
label: step.ctaLabel ?? fallbackActionLabel ?? '',
|
|
onClick: () => step.onAction?.(),
|
|
disabled: !step.onAction,
|
|
}
|
|
}
|
|
/>
|
|
);
|
|
})}
|
|
</div>
|
|
{steps.length === 0 && emptyCopy ? (
|
|
<p className="text-sm text-slate-600 dark:text-slate-300">{emptyCopy}</p>
|
|
) : null}
|
|
</CardContent>
|
|
</FrostedCard>
|
|
);
|
|
}
|