All files / src/components/dashboard ProfileCompletenessCard.tsx

100% Statements 14/14
100% Branches 16/16
100% Functions 4/4
100% Lines 14/14

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116                                31x   31x 23x 23x   1x       31x 2x 2x 2x 2x         31x 1x     30x                                                                         180x                                                                              
import { Link } from "@tanstack/react-router";
import { Check, ChevronDown, ChevronUp, Circle } from "lucide-react";
import { useState } from "react";
import type { Pro } from "../../lib/api";
import { getProfileCompleteness } from "../../lib/profile-completeness";
import { Card, CardContent, CardHeader, CardTitle } from "../ui/card";
 
interface ProfileCompletenessCardProps {
	pro: Pro;
	projectCount: number;
}
 
export function ProfileCompletenessCard({
	pro,
	projectCount,
}: ProfileCompletenessCardProps) {
	const { sections, percentage } = getProfileCompleteness(pro, projectCount);
 
	const [collapsed, setCollapsed] = useState(() => {
		try {
			return localStorage.getItem("profile-card-collapsed") === "true";
		} catch {
			return false;
		}
	});
 
	const toggleCollapsed = () => {
		const next = !collapsed;
		setCollapsed(next);
		try {
			localStorage.setItem("profile-card-collapsed", String(next));
		} catch {}
	};
 
	// Only show if profile is incomplete
	if (percentage >= 100) {
		return null;
	}
 
	return (
		<Card className="border-primary-200 bg-primary-50">
			<CardHeader className="pb-4">
				<div className="flex items-center justify-between">
					<CardTitle className="text-foreground-default">
						Complete Your Profile
					</CardTitle>
					<div className="flex items-center gap-2">
						<span className="text-2xl font-bold text-primary-600">
							{percentage}%
						</span>
						<button
							type="button"
							onClick={toggleCollapsed}
							className="p-1 text-foreground-muted hover:text-foreground-default sm:hidden"
							aria-label={collapsed ? "Expand profile checklist" : "Collapse profile checklist"}
						>
							{collapsed ? <ChevronDown className="h-5 w-5" /> : <ChevronUp className="h-5 w-5" />}
						</button>
					</div>
				</div>
				{/* Progress bar */}
				<div className="mt-3 h-2 w-full bg-background-muted rounded-full overflow-hidden">
					<div
						className="h-full bg-primary-500 transition-all duration-500"
						style={{ width: `${percentage}%` }}
					/>
				</div>
			</CardHeader>
			<CardContent>
				<div className={collapsed ? "hidden sm:block" : ""}>
					<p className="text-sm text-foreground-muted mb-4">
						A complete profile helps customers find and trust your business.
						Complete these sections to boost your visibility.
					</p>
					<div className="space-y-3">
						{sections.map((section) => (
							<div
								key={section.name}
								className="flex items-center justify-between"
							>
								<div className="flex items-center gap-2">
									{section.completed ? (
										<div className="flex items-center justify-center w-5 h-5 rounded-full bg-success">
											<Check className="h-3 w-3 text-foreground-inverse" />
										</div>
									) : (
										<Circle className="h-5 w-5 text-foreground-subtle" />
									)}
									<span
										className={
											section.completed
												? "text-foreground-muted line-through"
												: "text-foreground-default"
										}
									>
										{section.name}
									</span>
								</div>
								{!section.completed && (
									<Link
										to={section.href}
										search={section.tab ? { tab: section.tab } : {}}
										className="text-sm text-primary-600 hover:text-primary-700 font-medium"
									>
										Add details
									</Link>
								)}
							</div>
						))}
					</div>
				</div>
			</CardContent>
		</Card>
	);
}