kyd-shared-badge 0.3.110 → 0.3.112
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -102,8 +102,8 @@ const ReportHeader = ({ badgeId, developerName, updatedAt, score = 0, badgeImage
|
|
|
102
102
|
<div className={`flex gap-3 ${rightBadgeLayout ? 'flex-col md:flex-row items-center justify-between' : 'flex-col'}`}>
|
|
103
103
|
{/* Info section: Title, Candidate, Details and Summary */}
|
|
104
104
|
<div className={`w-full ${rightBadgeLayout ? 'md:flex-1' : ''}`}>
|
|
105
|
-
<div className='space-y-
|
|
106
|
-
<span className='flex gap-2 w-full items-end text-start justify-start'>
|
|
105
|
+
<div className='space-y-1'>
|
|
106
|
+
<span className='flex gap-2 w-full items-end text-start justify-start mb-2'>
|
|
107
107
|
<h2 className={'text-xl font-light'} style={{ color: 'var(--text-main)' }}>KYD Candidate:</h2>
|
|
108
108
|
<div className={'text-xl font-bold'} style={{ color: 'var(--text-main)' }}>{developerName || 'N/A'}</div>
|
|
109
109
|
</span>
|
|
@@ -111,14 +111,13 @@ const ReportHeader = ({ badgeId, developerName, updatedAt, score = 0, badgeImage
|
|
|
111
111
|
<h3 className='text-sm font-semibold text-[--text-secondary]'>Date:</h3>
|
|
112
112
|
<span className='text-sm text-[--text-main]'>{formatLocalDate(updatedAt)}</span>
|
|
113
113
|
</span>
|
|
114
|
-
<div className={'text-sm'}>
|
|
115
114
|
{Array.isArray(countries) && countries.length > 0 && (
|
|
116
115
|
(() => {
|
|
117
116
|
const countryNames = countries
|
|
118
117
|
.map(code => countriesLib.getName((code || '').toUpperCase(), 'en') || code)
|
|
119
118
|
.filter(Boolean);
|
|
120
119
|
return (
|
|
121
|
-
<p className={'flex items-center gap-1'}>
|
|
120
|
+
<p className={'flex items-center gap-1 text-sm'}>
|
|
122
121
|
<span className={'font-semibold'} style={{ color: 'var(--text-secondary)' }}>
|
|
123
122
|
{countryNames.length > 1 ? 'Countries:' : 'Country:'}
|
|
124
123
|
</span>
|
|
@@ -138,7 +137,7 @@ const ReportHeader = ({ badgeId, developerName, updatedAt, score = 0, badgeImage
|
|
|
138
137
|
})()
|
|
139
138
|
)}
|
|
140
139
|
{sources.length > 0 && (
|
|
141
|
-
<p className=
|
|
140
|
+
<p className='flex items-center gap-2 mt-2 text-sm'>
|
|
142
141
|
<a href="#appendix-connected" className={'inline-flex items-center gap-2 group'} style={{ color: 'var(--text-secondary)' }}>
|
|
143
142
|
<span className={'font-semibold'}>Sources:</span>
|
|
144
143
|
<span className={'flex items-center gap-2'}>
|
|
@@ -151,7 +150,6 @@ const ReportHeader = ({ badgeId, developerName, updatedAt, score = 0, badgeImage
|
|
|
151
150
|
</a>
|
|
152
151
|
</p>
|
|
153
152
|
)}
|
|
154
|
-
</div>
|
|
155
153
|
{(enterpriseMatch?.description || matchLabel) ? (
|
|
156
154
|
<div className={'hidden md:block text-sm space-y-2 pt-4'} style={{ borderTop: '1px solid var(--icon-button-secondary)' }}>
|
|
157
155
|
<div className="flex items-center justify-between">
|
|
@@ -190,7 +188,7 @@ const ReportHeader = ({ badgeId, developerName, updatedAt, score = 0, badgeImage
|
|
|
190
188
|
|
|
191
189
|
{/* Badge Image with robust centered overlay */}
|
|
192
190
|
<div className={`flex items-center ${rightBadgeLayout ? 'md:justify-end h-20' : 'justify-center w-full mt-4'} `}>
|
|
193
|
-
<div className={`relative w-full ${rightBadgeLayout ? 'max-w-[85px]' : 'px-
|
|
191
|
+
<div className={`relative w-full ${rightBadgeLayout ? 'max-w-[85px]' : 'px-28'} select-none`}>
|
|
194
192
|
<Image
|
|
195
193
|
src={finalBadgeImageUrl}
|
|
196
194
|
alt="KYD Badge"
|
|
@@ -29,6 +29,14 @@ function asText(value: unknown): string {
|
|
|
29
29
|
return String(value);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
function hasText(value: unknown): boolean {
|
|
33
|
+
return asText(value).trim().length > 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function hasAnyText(...values: unknown[]): boolean {
|
|
37
|
+
return values.some(hasText);
|
|
38
|
+
}
|
|
39
|
+
|
|
32
40
|
const Row = ({ label, value }: { label: string; value?: string }) => {
|
|
33
41
|
if (!value) return null;
|
|
34
42
|
return (
|
|
@@ -54,6 +62,34 @@ export default function ResumeView({ resume, roleName, badgeId, isPublic, showDo
|
|
|
54
62
|
|
|
55
63
|
const canDownload = !!(showDownloadButton && badgeId);
|
|
56
64
|
|
|
65
|
+
// Determine which sections have meaningful content and filter empty entries
|
|
66
|
+
const filteredSkillCategories = Array.isArray(skills?.categories)
|
|
67
|
+
? (skills.categories as any[]).filter((cat: any) => Array.isArray(cat?.items) && cat.items.some(hasText))
|
|
68
|
+
: [];
|
|
69
|
+
const hasSkillsSection = filteredSkillCategories.length > 0;
|
|
70
|
+
|
|
71
|
+
const filteredExperience = experience.filter((exp: any) =>
|
|
72
|
+
hasAnyText(exp?.title, exp?.company, exp?.startDate, exp?.endDate) ||
|
|
73
|
+
(Array.isArray(exp?.highlights) && exp.highlights.some(hasText)) ||
|
|
74
|
+
(Array.isArray(exp?.technologies) && exp.technologies.some(hasText))
|
|
75
|
+
);
|
|
76
|
+
const hasExperienceSection = filteredExperience.length > 0;
|
|
77
|
+
|
|
78
|
+
const filteredProjects = projects.filter((p: any) =>
|
|
79
|
+
hasAnyText(p?.name, p?.description, p?.impact, p?.link) ||
|
|
80
|
+
(Array.isArray(p?.technologies) && p.technologies.some(hasText))
|
|
81
|
+
);
|
|
82
|
+
const hasProjectsSection = filteredProjects.length > 0;
|
|
83
|
+
|
|
84
|
+
const filteredEducation = education.filter((e: any) => hasAnyText(e?.degree, e?.institution, e?.graduationDate));
|
|
85
|
+
const hasEducationSection = filteredEducation.length > 0;
|
|
86
|
+
|
|
87
|
+
const filteredCertifications = certifications.filter((c: any) => hasAnyText(c?.name, c?.issuer, c?.date));
|
|
88
|
+
const hasCertificationsSection = filteredCertifications.length > 0;
|
|
89
|
+
|
|
90
|
+
const filteredLinks = links.filter((l: any) => hasAnyText(l?.label, l?.url));
|
|
91
|
+
const hasLinksSection = filteredLinks.length > 0;
|
|
92
|
+
|
|
57
93
|
const handleDownloadResume = async () => {
|
|
58
94
|
if (!badgeId) return;
|
|
59
95
|
try {
|
|
@@ -113,22 +149,26 @@ export default function ResumeView({ resume, roleName, badgeId, isPublic, showDo
|
|
|
113
149
|
</div>
|
|
114
150
|
) : null}
|
|
115
151
|
|
|
116
|
-
{
|
|
152
|
+
{hasSkillsSection ? (
|
|
117
153
|
<div>
|
|
118
154
|
<div className={'text-lg font-semibold mb-2'} style={{ color: 'var(--text-main)' }}>Skills</div>
|
|
119
155
|
<div className={'space-y-1'}>
|
|
120
|
-
{
|
|
121
|
-
<Row
|
|
156
|
+
{filteredSkillCategories.map((cat: any, idx: number) => (
|
|
157
|
+
<Row
|
|
158
|
+
key={idx}
|
|
159
|
+
label={asText(cat?.name)}
|
|
160
|
+
value={(Array.isArray(cat?.items) ? cat.items.filter(hasText) : []).join(', ')}
|
|
161
|
+
/>
|
|
122
162
|
))}
|
|
123
163
|
</div>
|
|
124
164
|
</div>
|
|
125
165
|
) : null}
|
|
126
166
|
|
|
127
|
-
{
|
|
167
|
+
{hasExperienceSection ? (
|
|
128
168
|
<div>
|
|
129
169
|
<div className={'text-lg font-semibold mb-2'} style={{ color: 'var(--text-main)' }}>Experience</div>
|
|
130
170
|
<div className={'space-y-4'}>
|
|
131
|
-
{
|
|
171
|
+
{filteredExperience.map((exp: any, idx: number) => (
|
|
132
172
|
<div key={idx} className={'space-y-1'}>
|
|
133
173
|
<div className={'font-semibold'} style={{ color: 'var(--text-main)' }}>
|
|
134
174
|
{asText(exp?.title)}{asText(exp?.company) ? ` — ${asText(exp?.company)}` : ''}
|
|
@@ -136,15 +176,15 @@ export default function ResumeView({ resume, roleName, badgeId, isPublic, showDo
|
|
|
136
176
|
<div className={'text-xs'} style={{ color: 'var(--text-secondary)' }}>
|
|
137
177
|
{(asText(exp?.startDate) || asText(exp?.endDate)) ? `${asText(exp?.startDate)} – ${asText(exp?.endDate) || 'Present'}` : ''}
|
|
138
178
|
</div>
|
|
139
|
-
{Array.isArray(exp?.highlights) && exp.highlights.length > 0 ? (
|
|
179
|
+
{Array.isArray(exp?.highlights) && exp.highlights.filter(hasText).length > 0 ? (
|
|
140
180
|
<ul className={'list-disc pl-5 text-sm'} style={{ color: 'var(--text-secondary)' }}>
|
|
141
|
-
{exp.highlights.map((h: any, i: number) => (
|
|
181
|
+
{exp.highlights.filter(hasText).map((h: any, i: number) => (
|
|
142
182
|
<li key={i}>{asText(h)}</li>
|
|
143
183
|
))}
|
|
144
184
|
</ul>
|
|
145
185
|
) : null}
|
|
146
|
-
{Array.isArray(exp?.technologies) && exp.technologies.length > 0 ? (
|
|
147
|
-
<div className={'text-xs'} style={{ color: 'var(--text-secondary)' }}>Technologies: {exp.technologies.join(', ')}</div>
|
|
186
|
+
{Array.isArray(exp?.technologies) && exp.technologies.filter(hasText).length > 0 ? (
|
|
187
|
+
<div className={'text-xs'} style={{ color: 'var(--text-secondary)' }}>Technologies: {exp.technologies.filter(hasText).join(', ')}</div>
|
|
148
188
|
) : null}
|
|
149
189
|
</div>
|
|
150
190
|
))}
|
|
@@ -152,17 +192,17 @@ export default function ResumeView({ resume, roleName, badgeId, isPublic, showDo
|
|
|
152
192
|
</div>
|
|
153
193
|
) : null}
|
|
154
194
|
|
|
155
|
-
{
|
|
195
|
+
{hasProjectsSection ? (
|
|
156
196
|
<div>
|
|
157
197
|
<div className={'text-lg font-semibold mb-2'} style={{ color: 'var(--text-main)' }}>Projects</div>
|
|
158
198
|
<div className={'space-y-4'}>
|
|
159
|
-
{
|
|
199
|
+
{filteredProjects.map((p: any, idx: number) => (
|
|
160
200
|
<div key={idx} className={'space-y-1'}>
|
|
161
201
|
<div className={'font-semibold'} style={{ color: 'var(--text-main)' }}>{asText(p?.name)}</div>
|
|
162
202
|
{asText(p?.description) ? <div className={'text-sm'} style={{ color: 'var(--text-secondary)' }}>{asText(p?.description)}</div> : null}
|
|
163
203
|
{asText(p?.impact) ? <div className={'text-sm'} style={{ color: 'var(--text-secondary)' }}>Impact: {asText(p?.impact)}</div> : null}
|
|
164
|
-
{Array.isArray(p?.technologies) && p.technologies.length > 0 ? (
|
|
165
|
-
<div className={'text-xs'} style={{ color: 'var(--text-secondary)' }}>Technologies: {p.technologies.join(', ')}</div>
|
|
204
|
+
{Array.isArray(p?.technologies) && p.technologies.filter(hasText).length > 0 ? (
|
|
205
|
+
<div className={'text-xs'} style={{ color: 'var(--text-secondary)' }}>Technologies: {p.technologies.filter(hasText).join(', ')}</div>
|
|
166
206
|
) : null}
|
|
167
207
|
{asText(p?.link) ? <div className={'text-xs'} style={{ color: 'var(--text-secondary)' }}>{asText(p?.link)}</div> : null}
|
|
168
208
|
</div>
|
|
@@ -171,11 +211,11 @@ export default function ResumeView({ resume, roleName, badgeId, isPublic, showDo
|
|
|
171
211
|
</div>
|
|
172
212
|
) : null}
|
|
173
213
|
|
|
174
|
-
{
|
|
214
|
+
{hasEducationSection ? (
|
|
175
215
|
<div>
|
|
176
216
|
<div className={'text-lg font-semibold mb-2'} style={{ color: 'var(--text-main)' }}>Education</div>
|
|
177
217
|
<div className={'space-y-2'}>
|
|
178
|
-
{
|
|
218
|
+
{filteredEducation.map((e: any, idx: number) => (
|
|
179
219
|
<div key={idx} className={'text-sm'} style={{ color: 'var(--text-secondary)' }}>
|
|
180
220
|
{`${asText(e?.degree)} — ${asText(e?.institution)}${asText(e?.graduationDate) ? ` (${asText(e?.graduationDate)})` : ''}`}
|
|
181
221
|
</div>
|
|
@@ -184,11 +224,11 @@ export default function ResumeView({ resume, roleName, badgeId, isPublic, showDo
|
|
|
184
224
|
</div>
|
|
185
225
|
) : null}
|
|
186
226
|
|
|
187
|
-
{
|
|
227
|
+
{hasCertificationsSection ? (
|
|
188
228
|
<div>
|
|
189
229
|
<div className={'text-lg font-semibold mb-2'} style={{ color: 'var(--text-main)' }}>Certifications</div>
|
|
190
230
|
<div className={'space-y-1'}>
|
|
191
|
-
{
|
|
231
|
+
{filteredCertifications.map((c: any, idx: number) => (
|
|
192
232
|
<div key={idx} className={'text-sm'} style={{ color: 'var(--text-secondary)' }}>
|
|
193
233
|
{`${asText(c?.name)} — ${asText(c?.issuer)}${asText(c?.date) ? ` (${asText(c?.date)})` : ''}`}
|
|
194
234
|
</div>
|
|
@@ -197,11 +237,11 @@ export default function ResumeView({ resume, roleName, badgeId, isPublic, showDo
|
|
|
197
237
|
</div>
|
|
198
238
|
) : null}
|
|
199
239
|
|
|
200
|
-
{
|
|
240
|
+
{hasLinksSection ? (
|
|
201
241
|
<div>
|
|
202
242
|
<div className={'text-lg font-semibold mb-2'} style={{ color: 'var(--text-main)' }}>Links</div>
|
|
203
243
|
<div className={'space-y-1'}>
|
|
204
|
-
{
|
|
244
|
+
{filteredLinks.map((l: any, idx: number) => (
|
|
205
245
|
<div key={idx} className={'text-sm'} style={{ color: 'var(--text-secondary)' }}>
|
|
206
246
|
{`${asText(l?.label)}: ${asText(l?.url)}`}
|
|
207
247
|
</div>
|
|
@@ -62,8 +62,8 @@ export default function RoleOverviewCard({
|
|
|
62
62
|
>
|
|
63
63
|
<div className="mb-3 px-5 pt-5 flex items-start justify-between gap-2">
|
|
64
64
|
<div>
|
|
65
|
-
<div className={'font-semibold'} style={{ color: 'var(--text-main)' }}>{title}</div>
|
|
66
|
-
<div className={'text-
|
|
65
|
+
<div className={'font-semibold text-xl'} style={{ color: 'var(--text-main)' }}>{title}</div>
|
|
66
|
+
<div className={'text-sm mt-1'} style={{ color: 'var(--text-secondary)' }}>How well the candidate aligns with the target role based on KYD evidence.</div>
|
|
67
67
|
</div>
|
|
68
68
|
<span className={'relative inline-flex items-center group cursor-help'} style={{ color: 'var(--text-secondary)' }}>
|
|
69
69
|
<FiInfo />
|
|
@@ -408,12 +408,12 @@ export default function SkillsBubble({ skillsCategoryRadar, skillsByCategory, sk
|
|
|
408
408
|
color: 'var(--text-secondary)'
|
|
409
409
|
}}
|
|
410
410
|
>
|
|
411
|
-
<div className="flex items-center gap-2">
|
|
412
|
-
<span className="inline-block h-3 w-3 rounded-full" style={{ background: green5 }} />
|
|
411
|
+
<div className="flex items-center gap-2 text-base">
|
|
412
|
+
<span className="inline-block h-3 w-3 rounded-full border border-[var(--text-secondary)]" style={{ background: green5 }} />
|
|
413
413
|
<span>Size = evidence count per category</span>
|
|
414
414
|
</div>
|
|
415
|
-
<div className="flex items-center gap-2 mt-1">
|
|
416
|
-
<span className="inline-block h-3 w-3 rounded-full
|
|
415
|
+
<div className="flex items-center gap-2 mt-1 text-base">
|
|
416
|
+
<span className="inline-block h-3 w-3 rounded-full" style={{ background: green1 }} />
|
|
417
417
|
<span>Color = experience (darker = more)</span>
|
|
418
418
|
</div>
|
|
419
419
|
</div>
|