kyd-shared-badge 0.3.109 → 0.3.111
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 +1 -1
- package/src/PrintableBadgeDisplay.tsx +1 -0
- package/src/SharedBadgeDisplay.tsx +1 -0
- package/src/components/GaugeCard.tsx +2 -4
- package/src/components/ReportHeader.tsx +16 -8
- package/src/components/ResumeView.tsx +59 -19
- package/src/components/RiskCard.tsx +2 -2
- package/src/components/RoleOverviewCard.tsx +5 -7
- package/src/components/SkillsBubble.tsx +4 -4
package/package.json
CHANGED
|
@@ -139,6 +139,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
|
|
|
139
139
|
countries={(assessmentResult?.screening_sources?.ip_risk_analysis?.raw_data?.countries) || []}
|
|
140
140
|
accountAuthenticity={assessmentResult?.account_authenticity}
|
|
141
141
|
companyName={badgeData.companyName}
|
|
142
|
+
rightBadgeLayout={!assessmentResult?.enterprise_match}
|
|
142
143
|
/>
|
|
143
144
|
</Reveal>
|
|
144
145
|
{/* Coaching / Evidence under header when present */}
|
|
@@ -226,6 +226,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless, selfCheck = false
|
|
|
226
226
|
companyName={badgeData.companyName}
|
|
227
227
|
sourcesProviders={(badgeData?.connectedAccounts || []).map(a => (a?.name || '').toLowerCase())}
|
|
228
228
|
selfCheck={selfCheck}
|
|
229
|
+
rightBadgeLayout={!hasEnterpriseMatch}
|
|
229
230
|
/>
|
|
230
231
|
</div>
|
|
231
232
|
{/* Top-right: Role match section */}
|
|
@@ -74,10 +74,8 @@ export default function GaugeCard({
|
|
|
74
74
|
>
|
|
75
75
|
<div className="mb-3 flex items-start justify-between gap-2 pt-5 px-5">
|
|
76
76
|
<div>
|
|
77
|
-
<div className={'font-semibold'} style={{ color: 'var(--text-main)' }}>{title}</div>
|
|
78
|
-
{
|
|
79
|
-
<div className={'text-xs mt-1'} style={{ color: 'var(--text-secondary)' }}>{description}</div>
|
|
80
|
-
) : null}
|
|
77
|
+
<div className={'font-semibold text-xl'} style={{ color: 'var(--text-main)' }}>{title}</div>
|
|
78
|
+
<div className={'text-sm mt-1'} style={{ color: 'var(--text-secondary)' }}>{description}</div>
|
|
81
79
|
</div>
|
|
82
80
|
{(tooltipText || description) && (
|
|
83
81
|
<span className={'relative inline-flex items-center group cursor-help'} style={{ color: 'var(--text-secondary)' }}>
|
|
@@ -52,9 +52,10 @@ interface ReportHeaderProps {
|
|
|
52
52
|
companyName?: string;
|
|
53
53
|
sourcesProviders?: string[];
|
|
54
54
|
selfCheck?: boolean;
|
|
55
|
+
rightBadgeLayout?: boolean;
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
const ReportHeader = ({ badgeId, developerName, updatedAt, score = 0, badgeImageUrl, summary, enterpriseMatch, countries = [], accountAuthenticity, companyName, sourcesProviders = [], selfCheck }: ReportHeaderProps) => {
|
|
58
|
+
const ReportHeader = ({ badgeId, developerName, updatedAt, score = 0, badgeImageUrl, summary, enterpriseMatch, countries = [], accountAuthenticity, companyName, sourcesProviders = [], selfCheck, rightBadgeLayout }: ReportHeaderProps) => {
|
|
58
59
|
// Use the dynamic image if available, otherwise fall back to the score-based one.
|
|
59
60
|
const finalBadgeImageUrl = badgeImageUrl || getBadgeImageUrl(score || 0);
|
|
60
61
|
const tint = hexToRgba(pickTint(score || 0, selfCheck), 0.06);
|
|
@@ -98,9 +99,9 @@ const ReportHeader = ({ badgeId, developerName, updatedAt, score = 0, badgeImage
|
|
|
98
99
|
</div>
|
|
99
100
|
);
|
|
100
101
|
})()}
|
|
101
|
-
<div className=
|
|
102
|
+
<div className={`flex gap-3 ${rightBadgeLayout ? 'flex-col md:flex-row items-center justify-between' : 'flex-col'}`}>
|
|
102
103
|
{/* Info section: Title, Candidate, Details and Summary */}
|
|
103
|
-
<div className=
|
|
104
|
+
<div className={`w-full ${rightBadgeLayout ? 'md:flex-1' : ''}`}>
|
|
104
105
|
<div className='space-y-2'>
|
|
105
106
|
<span className='flex gap-2 w-full items-end text-start justify-start'>
|
|
106
107
|
<h2 className={'text-xl font-light'} style={{ color: 'var(--text-main)' }}>KYD Candidate:</h2>
|
|
@@ -188,11 +189,18 @@ const ReportHeader = ({ badgeId, developerName, updatedAt, score = 0, badgeImage
|
|
|
188
189
|
</div>
|
|
189
190
|
|
|
190
191
|
{/* Badge Image with robust centered overlay */}
|
|
191
|
-
<div className=
|
|
192
|
-
<div className=
|
|
193
|
-
<Image
|
|
194
|
-
|
|
195
|
-
|
|
192
|
+
<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-28'} select-none`}>
|
|
194
|
+
<Image
|
|
195
|
+
src={finalBadgeImageUrl}
|
|
196
|
+
alt="KYD Badge"
|
|
197
|
+
width={rightBadgeLayout ? 25 : 100}
|
|
198
|
+
height={rightBadgeLayout ? 25 : 100}
|
|
199
|
+
priority
|
|
200
|
+
className='w-full h-auto pointer-events-none'
|
|
201
|
+
/>
|
|
202
|
+
<div className={`pointer-events-none absolute left-1/2 -translate-x-1/2 -translate-y-1/2 ${rightBadgeLayout ? 'top-[72%]' : 'top-[69%]'}`}>
|
|
203
|
+
<div className={`font-extrabold text-black ${rightBadgeLayout ? 'text-xs' : 'text-3xl'}`}>
|
|
196
204
|
{Math.round(score || 0)}%
|
|
197
205
|
</div>
|
|
198
206
|
</div>
|
|
@@ -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>
|
|
@@ -58,9 +58,9 @@ export default function RiskCard({
|
|
|
58
58
|
>
|
|
59
59
|
<div className="mb-3 flex items-start justify-between gap-2">
|
|
60
60
|
<div>
|
|
61
|
-
<div className={'font-semibold'} style={{ color: 'var(--text-main)' }}>{title}</div>
|
|
61
|
+
<div className={'font-semibold text-xl'} style={{ color: 'var(--text-main)' }}>{title}</div>
|
|
62
62
|
{description ? (
|
|
63
|
-
<div className={'text-
|
|
63
|
+
<div className={'text-sm mt-1'} style={{ color: 'var(--text-secondary)' }}>{description}</div>
|
|
64
64
|
) : null}
|
|
65
65
|
</div>
|
|
66
66
|
{(tooltipText || description) && (
|
|
@@ -31,7 +31,7 @@ export default function RoleOverviewCard({
|
|
|
31
31
|
|
|
32
32
|
const tickLabels = useMemo(() => ({
|
|
33
33
|
type: 'outer' as const,
|
|
34
|
-
hideMinMax:
|
|
34
|
+
hideMinMax: true,
|
|
35
35
|
defaultTickLineConfig: {
|
|
36
36
|
length: 7,
|
|
37
37
|
width: 1,
|
|
@@ -45,11 +45,9 @@ export default function RoleOverviewCard({
|
|
|
45
45
|
}
|
|
46
46
|
},
|
|
47
47
|
ticks: [
|
|
48
|
-
{ value:
|
|
49
|
-
{ value: 25, valueConfig: { formatTextValue: () => 'Weak' } },
|
|
48
|
+
{ value: 16, valueConfig: { formatTextValue: () => 'Weak' } },
|
|
50
49
|
{ value: 50, valueConfig: { formatTextValue: () => 'Partial' } },
|
|
51
|
-
{ value:
|
|
52
|
-
{ value: 100, valueConfig: { formatTextValue: () => 'Optimal' } },
|
|
50
|
+
{ value: 83, valueConfig: { formatTextValue: () => 'Strong' } },
|
|
53
51
|
]
|
|
54
52
|
}), []);
|
|
55
53
|
|
|
@@ -64,8 +62,8 @@ export default function RoleOverviewCard({
|
|
|
64
62
|
>
|
|
65
63
|
<div className="mb-3 px-5 pt-5 flex items-start justify-between gap-2">
|
|
66
64
|
<div>
|
|
67
|
-
<div className={'font-semibold'} style={{ color: 'var(--text-main)' }}>{title}</div>
|
|
68
|
-
<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>
|
|
69
67
|
</div>
|
|
70
68
|
<span className={'relative inline-flex items-center group cursor-help'} style={{ color: 'var(--text-secondary)' }}>
|
|
71
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>
|