kyd-shared-badge 0.3.45 → 0.3.47

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kyd-shared-badge",
3
- "version": "0.3.45",
3
+ "version": "0.3.47",
4
4
  "private": false,
5
5
  "main": "./src/index.ts",
6
6
  "module": "./src/index.ts",
@@ -137,6 +137,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
137
137
  })()}
138
138
  countries={(assessmentResult?.screening_sources?.ip_risk_analysis?.raw_data?.countries) || []}
139
139
  accountAuthenticity={assessmentResult?.account_authenticity}
140
+ companyName={badgeData.companyName}
140
141
  />
141
142
  </Reveal>
142
143
  {/* Coaching / Evidence under header when present */}
@@ -31,6 +31,7 @@ import AiUsageBody from './components/AiUsageBody';
31
31
  import SanctionsMatches from './components/SanctionsMatches';
32
32
  import AppendixContent from './components/AppendixContent';
33
33
  import { ProviderIcon, getProviderDisplayName, getProviderTooltipCopy, getCategoryTooltipCopy, barColor } from './utils/provider';
34
+ import { matchLabelToColor } from './colors';
34
35
  import { useEffect, useMemo, useState } from 'react';
35
36
  type ChatWidgetProps = Partial<{
36
37
  api: string;
@@ -114,7 +115,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
114
115
  const tabs = useMemo(() => {
115
116
  const arr = [
116
117
  { key: 'overview', label: 'Overview' },
117
- ...(showRoleFit ? [{ key: 'role', label: 'Role Fit & Coaching' }] : []),
118
+ { key: 'role', label: 'Role Fit & Coaching' },
118
119
  { key: 'technical', label: 'KYD Technical' },
119
120
  { key: 'risk', label: 'KYD Risk' },
120
121
  { key: 'ai', label: 'KYD AI' },
@@ -132,7 +133,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
132
133
  const keys = new Set(tabs.map(t => t.key));
133
134
  if (hash && keys.has(hash)) setActiveTab(hash);
134
135
  // If the hash targets a skills appendix anchor, switch to Appendix
135
- if (hash && (hash.startsWith('appendix-skills') || hash.includes('appendix-skills-cat-') || hash === 'appendix-connected')) {
136
+ if (hash && (hash.startsWith('appendix-skills') || hash.includes('appendix-skills-cat-'))) {
136
137
  setActiveTab('appendix');
137
138
  }
138
139
  } catch {}
@@ -215,8 +216,12 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
215
216
  </div>
216
217
  );
217
218
 
218
- const RoleFitSection = () => (
219
- !showRoleFit ? null : (
219
+ const RoleFitSection = () => {
220
+ // Local state for expanding long role descriptions
221
+ const [showFullRoleDescription, setShowFullRoleDescription] = useState(false);
222
+ const rec = assessmentResult?.recommendations;
223
+
224
+ return (
220
225
  <div className={`${wrapperMaxWidth} mx-auto`}>
221
226
  <div className={'rounded-xl shadow-xl p-6 sm:p-8 mt-6 border'} style={{ backgroundColor: 'var(--content-card-background)', borderColor: 'var(--icon-button-secondary)' }}>
222
227
  <div className="space-y-10">
@@ -224,19 +229,46 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
224
229
  <div>
225
230
  <Reveal headless={isHeadless} as={'h4'} offsetY={8} className={'text-2xl font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>Role Alignment</Reveal>
226
231
  <Reveal headless={isHeadless}>
227
- <div className={'text-sm'} style={{ color: 'var(--text-secondary)' }}>
228
- {(() => {
229
- const em = assessmentResult?.enterprise_match;
230
- if (!em) return null;
231
- const role = em.role || {};
232
- return (
233
- <div>
234
- <div className={'font-medium mb-1'} style={{ color: 'var(--text-main)' }}>{em.label} {role?.name ? `— ${role.name}` : ''}</div>
235
- {em.description ? <div>{em.description}</div> : null}
232
+ {(() => {
233
+ const em = assessmentResult?.enterprise_match;
234
+ if (!em) return null;
235
+ const role = em.role || {};
236
+ const roleName = role?.name || '';
237
+ const primaryDescription = (role?.description || em.description || '').trim();
238
+ const isLong = primaryDescription.length > 500;
239
+ const visibleText = isLong && !showFullRoleDescription ? `${primaryDescription.slice(0, 500)}…` : primaryDescription;
240
+ return (
241
+ <div className={'text-sm'} style={{ color: 'var(--text-secondary)' }}>
242
+ <div className="flex items-center text-start gap-2 py-3">
243
+ <div className={'text-base font-semibold'} style={{ color: 'var(--text-main)' }}>{roleName}</div>
244
+ <span>-</span>
245
+ <div
246
+ className={
247
+ `px-3 py-1 inline-block rounded-md font-semibold shadow-sm text-[var(--text-main)] border`
248
+ }
249
+ style={{ borderColor: matchLabelToColor(em.label) }}
250
+ >
251
+ {em.label}
252
+ </div>
236
253
  </div>
237
- );
238
- })()}
239
- </div>
254
+ {primaryDescription ? (
255
+ <div>
256
+ <span>{visibleText}</span>
257
+ {isLong && (
258
+ <button
259
+ type="button"
260
+ onClick={() => setShowFullRoleDescription(v => !v)}
261
+ className={'ml-2 text-xs font-semibold'}
262
+ style={{ color: 'var(--text-main)' }}
263
+ >
264
+ {showFullRoleDescription ? 'See less' : 'See more'}
265
+ </button>
266
+ )}
267
+ </div>
268
+ ) : null}
269
+ </div>
270
+ );
271
+ })()}
240
272
  </Reveal>
241
273
  </div>
242
274
  )}
@@ -258,11 +290,30 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
258
290
  <UseCases useCases={assessmentResult?.enterprise_use_cases} headless={isHeadless} badgeId={badgeId} />
259
291
  </div>
260
292
  )}
293
+ <div>
294
+ <Reveal headless={isHeadless} as={'h4'} offsetY={8} className={'text-2xl font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>Recommendations</Reveal>
295
+ <Reveal headless={isHeadless}>
296
+ <div className={'space-y-3'}>
297
+ {rec?.summary ? (
298
+ <div className={'text-sm'} style={{ color: 'var(--text-secondary)' }}>{rec.summary}</div>
299
+ ) : null}
300
+ {Array.isArray(rec?.bullet_points) && (rec?.bullet_points?.length || 0) > 0 ? (
301
+ <ul className={'list-disc pl-5 text-sm'} style={{ color: 'var(--text-secondary)' }}>
302
+ {rec?.bullet_points?.map((bp, idx) => (
303
+ <li key={idx}>{bp}</li>
304
+ ))}
305
+ </ul>
306
+ ) : (!rec?.summary ? (
307
+ <div className={'text-sm'} style={{ color: 'var(--text-secondary)' }}>No recommendations available.</div>
308
+ ) : null)}
309
+ </div>
310
+ </Reveal>
311
+ </div>
261
312
  </div>
262
313
  </div>
263
314
  </div>
264
- )
265
- );
315
+ );
316
+ };
266
317
 
267
318
  const TechnicalSection = () => (
268
319
  <div className={`${wrapperMaxWidth} mx-auto`}>
@@ -428,7 +479,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
428
479
  })()}
429
480
  </div>
430
481
  )}
431
- <div id="appendix-connected">
482
+ <div>
432
483
  <ConnectedPlatforms accounts={connected} authenticity={assessmentResult?.account_authenticity} />
433
484
  </div>
434
485
  </div>
@@ -461,7 +512,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
461
512
  </style>
462
513
  {/* Long-form original layout retained for headless/print */}
463
514
  {OverviewSection()}
464
- {showRoleFit ? RoleFitSection() : null}
515
+ <RoleFitSection />
465
516
  {TechnicalSection()}
466
517
  {RiskSection()}
467
518
  {AiSection()}
@@ -472,7 +523,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
472
523
  <TabNav />
473
524
  <div className={'px-2 sm:px-0 pb-2'}>
474
525
  {activeTab === 'overview' && OverviewSection()}
475
- {activeTab === 'role' && RoleFitSection()}
526
+ {activeTab === 'role' && <RoleFitSection />}
476
527
  {activeTab === 'technical' && TechnicalSection()}
477
528
  {activeTab === 'risk' && RiskSection()}
478
529
  {activeTab === 'ai' && AiSection()}
package/src/colors.ts CHANGED
@@ -96,5 +96,11 @@ export function clampPercent(value?: number): number {
96
96
  return `rgba(${r}, ${g}, ${b}, ${alpha})`
97
97
  }
98
98
 
99
+ export function matchLabelToColor(label: string): string {
100
+ const l = (label || '').toLowerCase();
101
+ if (l.includes('optimal') || l.includes('strong')) return green
102
+ if (l.includes('moderate')) return yellow
103
+ return red
104
+ }
99
105
 
100
106
 
@@ -62,12 +62,13 @@ const ConnectedPlatforms = ({ accounts, authenticity }: { accounts?: ConnectedAc
62
62
  const label = (authenticity?.label || '').toLowerCase();
63
63
  const isCritical = label === 'likely inauthentic';
64
64
  const isSuspicious = label === 'suspicious';
65
+ const displayLabel = isSuspicious ? 'Warning' : label
65
66
  if (!label) return null;
66
67
  return (
67
68
  <div className={"mb-4 rounded-md border p-3"} style={{ backgroundColor: 'var(--content-card-background)', borderColor: 'var(--icon-button-secondary)' }}>
68
69
  <div className="flex items-start gap-2">
69
70
  <span className="text-sm font-semibold" style={{ color: isCritical ? '#B00020' : isSuspicious ? '#9f580a' : 'var(--text-main)' }}>
70
- {(authenticity?.label) || ''}
71
+ {displayLabel}
71
72
  </span>
72
73
  </div>
73
74
  {authenticity?.description ? (
@@ -40,9 +40,10 @@ interface ReportHeaderProps {
40
40
  enterpriseMatch?: { label?: string; description?: string; roleName?: string } | null;
41
41
  countries?: string[];
42
42
  accountAuthenticity?: { label?: string; description?: string };
43
+ companyName?: string;
43
44
  }
44
45
 
45
- const ReportHeader = ({ badgeId, developerName, updatedAt, score = 0, badgeImageUrl, summary, enterpriseMatch, countries = [], accountAuthenticity }: ReportHeaderProps) => {
46
+ const ReportHeader = ({ badgeId, developerName, updatedAt, score = 0, badgeImageUrl, summary, enterpriseMatch, countries = [], accountAuthenticity, companyName }: ReportHeaderProps) => {
46
47
  // Use the dynamic image if available, otherwise fall back to the score-based one.
47
48
  const finalBadgeImageUrl = badgeImageUrl || getBadgeImageUrl(score || 0);
48
49
  const tint = hexToRgba(pickTint(score || 0), 0.06);
@@ -102,7 +103,7 @@ const ReportHeader = ({ badgeId, developerName, updatedAt, score = 0, badgeImage
102
103
  <div className={'text-sm space-y-2'}>
103
104
  <p><span className={'font-semibold'} style={{ color: 'var(--text-secondary)' }}>Developer:</span> <span style={{ color: 'var(--text-main)' }}>{developerName || 'N/A'}</span></p>
104
105
  <p><span className={'font-semibold'} style={{ color: 'var(--text-secondary)' }}>Requested By:</span> <span style={{ color: 'var(--text-main)' }}>{developerName || 'N/A'}</span></p>
105
- <p><span className={'font-semibold'} style={{ color: 'var(--text-secondary)' }}>Organization:</span> <span style={{ color: 'var(--text-main)' }}>Unaffiliated</span></p>
106
+ <p><span className={'font-semibold'} style={{ color: 'var(--text-secondary)' }}>Organization:</span> <span style={{ color: 'var(--text-main)' }}>{companyName || 'Unaffiliated'}</span></p>
106
107
  <p><span className={'font-semibold'} style={{ color: 'var(--text-secondary)' }}>Date Generated:</span> <span style={{ color: 'var(--text-main)' }}>{formattedDate}</span></p>
107
108
  <p><span className={'font-semibold'} style={{ color: 'var(--text-secondary)' }}>Report ID:</span> <span style={{ color: 'var(--text-main)' }}>{badgeId || 'N/A'}</span></p>
108
109
  {Array.isArray(countries) && countries.length > 0 && (
package/src/types.ts CHANGED
@@ -34,6 +34,7 @@ export interface PublicBadgeData {
34
34
  }[];
35
35
  optOutScreening?: boolean;
36
36
  isPublic?: boolean;
37
+ companyName?: string;
37
38
  }
38
39
 
39
40
  export type VerifiedBadge = {
@@ -172,7 +173,7 @@ export interface EnterpriseMatch {
172
173
  description: string;
173
174
  categoryTargets?: Record<string, number>;
174
175
  };
175
- label: string;
176
+ label: 'Incompatible' | 'Weak Match' | 'Moderate Match' | 'Strong Match' | 'Optimal Match';
176
177
  description: string;
177
178
  coaching?: EnterpriseCoaching;
178
179
  }