Sifa professional network frontend (Next.js, React, TailwindCSS)
sifa.id/
1'use client';
2
3import { useTranslations } from 'next-intl';
4import { EditableSection, EditableEntry, CERTIFICATION_FIELDS } from '@/components/profile-editor';
5import {
6 certificationToValues,
7 valuesToCertification,
8} from '@/components/profile-editor/section-converters';
9import { formatTimelineDate } from './timeline';
10import { sortByDateDesc, certDateExtractor } from '@/lib/sort-by-date';
11import type { ProfileCertification } from '@/lib/types';
12
13interface CredentialsSectionProps {
14 certifications: ProfileCertification[];
15 isOwnProfile?: boolean;
16}
17
18export function CredentialsSection({ certifications, isOwnProfile }: CredentialsSectionProps) {
19 const t = useTranslations('sections');
20
21 if (!certifications.length && !isOwnProfile) return null;
22
23 return (
24 <section className="mt-8" aria-label={t('credentials')}>
25 <h2 className="mb-4 text-xl font-semibold">
26 {t('credentials')}
27 {certifications.length > 0 && (
28 <span className="ml-2 text-sm font-normal text-muted-foreground">
29 {certifications.length}
30 </span>
31 )}
32 </h2>
33 <EditableSection<ProfileCertification>
34 maxVisible={5}
35 sectionTitle={t('credentials')}
36 profileKey="certifications"
37 isOwnProfile={isOwnProfile}
38 fields={CERTIFICATION_FIELDS}
39 toValues={certificationToValues}
40 fromValues={
41 valuesToCertification as unknown as (
42 v: Record<string, string | boolean>,
43 ) => Omit<ProfileCertification, 'rkey'>
44 }
45 collection="id.sifa.profile.certification"
46 sortItems={(items) => sortByDateDesc(items, certDateExtractor)}
47 renderEntry={(cert, controls) => (
48 <EditableEntry
49 key={cert.rkey}
50 isOwnProfile={isOwnProfile}
51 onEdit={controls?.onEdit ?? (() => {})}
52 onDelete={controls?.onDelete ?? (() => {})}
53 entryLabel={cert.name}
54 >
55 <div className="flex items-start justify-between gap-2">
56 <div className="min-w-0">
57 <p className="font-medium">
58 {cert.credentialUrl ? (
59 <a
60 href={cert.credentialUrl}
61 className="underline-offset-4 hover:underline"
62 target="_blank"
63 rel="noopener noreferrer"
64 >
65 {cert.name}
66 </a>
67 ) : (
68 cert.name
69 )}
70 </p>
71 <p className="text-sm text-muted-foreground">{cert.issuingOrg}</p>
72 </div>
73 {cert.issueDate && (
74 <span className="shrink-0 text-xs text-muted-foreground">
75 {formatTimelineDate(cert.issueDate)}
76 </span>
77 )}
78 </div>
79 </EditableEntry>
80 )}
81 />
82 </section>
83 );
84}