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 124 125 126 127 128 129 130 131 132 133 | 8x 267x 267x 77x 267x 77x 267x 267x 77x 2x 3x | import { Award, Plus, Edit2, Trash2 } from "lucide-react";
import {
Card,
CardContent,
CardHeader,
CardTitle,
CardDescription,
} from "../ui/card";
import { Button } from "../ui/button";
import type { ProCertification } from "../../lib/api";
import { getImageUrl } from "../../lib/api/base";
const PER_TYPE_LIMIT = 10;
interface CertificationsSectionProps {
certifications: ProCertification[];
onAdd: () => void;
onEdit: (certification: ProCertification) => 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 CertificationsSection({
certifications,
onAdd,
onEdit,
onDelete,
canEdit = true,
}: CertificationsSectionProps) {
const awardCount = certifications.filter((c) => c.type === "award").length;
const certCount = certifications.filter(
(c) => c.type === "certification",
).length;
const membershipCount = certifications.filter(
(c) => c.type === "membership",
).length;
const allTypesFull =
awardCount >= PER_TYPE_LIMIT &&
certCount >= PER_TYPE_LIMIT &&
membershipCount >= PER_TYPE_LIMIT;
return (
<Card>
<CardHeader className="flex flex-row items-center justify-between">
<div>
<CardTitle className="flex items-center gap-2">
<Award className="h-5 w-5" />
Certifications & Awards
</CardTitle>
<CardDescription>
Awards {awardCount}/{PER_TYPE_LIMIT} • Certifications {certCount}/
{PER_TYPE_LIMIT} • Memberships {membershipCount}/{PER_TYPE_LIMIT}
</CardDescription>
</div>
{canEdit && !allTypesFull && (
<Button variant="outline" size="sm" onClick={onAdd}>
<Plus className="h-4 w-4 mr-1" />
Add
</Button>
)}
</CardHeader>
<CardContent>
{certifications.length === 0 ? (
<div className="text-center py-8 text-foreground-muted">
<Award className="h-12 w-12 mx-auto mb-2 text-foreground-subtle" />
<p>No certifications or awards added yet</p>
</div>
) : (
<div className="space-y-4">
{certifications.map((cert) => (
<div
key={cert.id}
className="flex items-start justify-between p-4 border rounded-lg"
>
<div className="flex items-start gap-3 min-w-0">
{cert.imageUrl ? (
<img
src={getImageUrl(cert.imageUrl)}
alt={cert.title}
className="h-14 w-14 rounded-md object-cover border border-default shrink-0"
/>
) : (
<div className="h-14 w-14 rounded-md bg-background-muted flex items-center justify-center text-xl shrink-0">
{cert.type === "award"
? "🏆"
: cert.type === "membership"
? "🤝"
: "📜"}
</div>
)}
<div className="min-w-0">
<div className="flex items-center gap-2 flex-wrap">
<h4 className="font-medium truncate">{cert.title}</h4>
<span className="px-2 py-0.5 text-xs rounded bg-background-muted text-foreground-muted capitalize">
{cert.type}
</span>
</div>
<p className="text-sm text-foreground-muted">
{cert.issuer && `By ${cert.issuer}`}
{cert.issuer && cert.year && " • "}
{cert.year}
</p>
</div>
</div>
{canEdit && (
<div className="flex gap-2 shrink-0">
<button
type="button"
onClick={() => onEdit(cert)}
className="p-1 text-foreground-subtle hover:text-info"
>
<Edit2 className="h-4 w-4" />
</button>
<button
type="button"
onClick={() => onDelete(cert.id)}
className="p-1 text-foreground-subtle hover:text-error"
>
<Trash2 className="h-4 w-4" />
</button>
</div>
)}
</div>
))}
</div>
)}
</CardContent>
</Card>
);
}
|