feat: complete Phase 3 — advanced features for Casera web app
Adds sharing (residence share codes, join, user management, .casera file export/import), subscription status with feature comparison, notification preferences with bell icon, profile settings (edit info, change password, theme picker, delete account), onboarding wizard with create/join paths, enhanced dashboard with stats cards, Recharts completion chart, recent activity feed, and task report PDF download. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { Phone, Mail, Star } from "lucide-react";
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription, CardAction } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import type { ContractorResponse } from "@/lib/api/contractors";
|
||||
|
||||
interface ContractorCardProps {
|
||||
contractor: ContractorResponse;
|
||||
onToggleFavorite: (id: number) => void;
|
||||
}
|
||||
|
||||
export function ContractorCard({ contractor, onToggleFavorite }: ContractorCardProps) {
|
||||
return (
|
||||
<Card className="transition-shadow hover:shadow-md">
|
||||
<CardHeader>
|
||||
<Link href={`/app/contractors/${contractor.id}`} className="hover:underline">
|
||||
<CardTitle>{contractor.name}</CardTitle>
|
||||
</Link>
|
||||
{contractor.company && (
|
||||
<CardDescription>{contractor.company}</CardDescription>
|
||||
)}
|
||||
<CardAction>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="size-8"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
onToggleFavorite(contractor.id);
|
||||
}}
|
||||
>
|
||||
<Star
|
||||
className={
|
||||
contractor.is_favorite
|
||||
? "size-4 fill-yellow-400 text-yellow-400"
|
||||
: "size-4 text-muted-foreground"
|
||||
}
|
||||
/>
|
||||
</Button>
|
||||
</CardAction>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{contractor.specialties.length > 0 && (
|
||||
<div className="flex flex-wrap gap-1 mb-3">
|
||||
{contractor.specialties.map((s) => (
|
||||
<Badge key={s.id} variant="secondary">
|
||||
{s.icon && <span className="mr-1">{s.icon}</span>}
|
||||
{s.name}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center gap-2">
|
||||
{contractor.phone && (
|
||||
<Button variant="outline" size="icon" className="size-8" asChild>
|
||||
<a href={`tel:${contractor.phone}`} onClick={(e) => e.stopPropagation()}>
|
||||
<Phone className="size-4" />
|
||||
</a>
|
||||
</Button>
|
||||
)}
|
||||
{contractor.email && (
|
||||
<Button variant="outline" size="icon" className="size-8" asChild>
|
||||
<a href={`mailto:${contractor.email}`} onClick={(e) => e.stopPropagation()}>
|
||||
<Mail className="size-4" />
|
||||
</a>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user