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 117 118 119 120 121 122 123 | 266x 46x 220x 1x 3x | import { MessageSquare, Plus, Edit2, Trash2 } from "lucide-react";
import {
Card,
CardContent,
CardHeader,
CardTitle,
CardDescription,
} from "../ui/card";
import { Button } from "../ui/button";
import type { ProTestimonial } from "../../lib/api";
interface TestimonialsSectionProps {
testimonials: ProTestimonial[];
onAdd: () => void;
onEdit: (testimonial: ProTestimonial) => void;
onDelete: (id: number) => void;
/** #424: gate Add/Edit/Delete on caller's edit permission. Default
* `true` preserves admin-page behavior. */
canEdit?: boolean;
}
export function TestimonialsSection({
testimonials,
onAdd,
onEdit,
onDelete,
canEdit = true,
}: TestimonialsSectionProps) {
return (
<Card>
<CardHeader className="flex flex-row items-center justify-between">
<div>
<CardTitle className="flex items-center gap-2">
<MessageSquare className="h-5 w-5" />
Customer Reviews
</CardTitle>
<CardDescription>
Share customer success stories (up to 10)
</CardDescription>
</div>
{canEdit && testimonials.length < 10 && (
<Button variant="outline" size="sm" onClick={onAdd}>
<Plus className="h-4 w-4 mr-1" />
Add
</Button>
)}
</CardHeader>
<CardContent>
{testimonials.length === 0 ? (
<div className="text-center py-8 text-foreground-muted">
<MessageSquare className="h-12 w-12 mx-auto mb-2 text-foreground-subtle" />
<p>No testimonials added yet</p>
<p className="text-sm">Customer reviews build credibility</p>
</div>
) : (
<div className="space-y-4">
{testimonials.map((testimonial) => (
<div key={testimonial.id} className="p-4 border rounded-lg">
<div className="flex items-start justify-between">
<div className="flex-1">
<p className="text-foreground-default italic">
“{testimonial.reviewText}”
</p>
<div className="mt-2 flex items-center gap-2">
<span className="font-medium">
{testimonial.customerName}
</span>
{testimonial.customerLocation && (
<span className="text-sm text-foreground-muted">
from {testimonial.customerLocation}
</span>
)}
{testimonial.projectType && (
<span className="text-sm text-foreground-subtle">
• {testimonial.projectType}
</span>
)}
</div>
{testimonial.rating && (
<div className="mt-1 flex">
{[1, 2, 3, 4, 5].map((star) => (
<span
key={`star-${testimonial.id}-${star}`}
className={
star <= (testimonial.rating ?? 0)
? "text-rating"
: "text-foreground-subtle"
}
>
★
</span>
))}
</div>
)}
</div>
{canEdit && (
<div className="flex gap-2 ml-4">
<button
type="button"
onClick={() => onEdit(testimonial)}
className="p-1 text-foreground-subtle hover:text-info"
>
<Edit2 className="h-4 w-4" />
</button>
<button
type="button"
onClick={() => onDelete(testimonial.id)}
className="p-1 text-foreground-subtle hover:text-error"
>
<Trash2 className="h-4 w-4" />
</button>
</div>
)}
</div>
</div>
))}
</div>
)}
</CardContent>
</Card>
);
}
|