kyd-shared-badge 0.3.44 → 0.3.46

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.44",
3
+ "version": "0.3.46",
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 */}
@@ -286,9 +287,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
286
287
 
287
288
  {/* Connected Platforms */}
288
289
  <Reveal headless={isHeadless}>
289
- <div id="appendix-connected">
290
- <ConnectedPlatforms accounts={connected} authenticity={assessmentResult?.account_authenticity} />
291
- </div>
290
+ <ConnectedPlatforms accounts={connected} authenticity={assessmentResult?.account_authenticity} />
292
291
  </Reveal>
293
292
 
294
293
 
@@ -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;
@@ -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,11 @@ 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
+
223
+ return !showRoleFit ? null : (
220
224
  <div className={`${wrapperMaxWidth} mx-auto`}>
221
225
  <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
226
  <div className="space-y-10">
@@ -224,19 +228,46 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
224
228
  <div>
225
229
  <Reveal headless={isHeadless} as={'h4'} offsetY={8} className={'text-2xl font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>Role Alignment</Reveal>
226
230
  <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}
231
+ {(() => {
232
+ const em = assessmentResult?.enterprise_match;
233
+ if (!em) return null;
234
+ const role = em.role || {};
235
+ const roleName = role?.name || '';
236
+ const primaryDescription = (role?.description || em.description || '').trim();
237
+ const isLong = primaryDescription.length > 500;
238
+ const visibleText = isLong && !showFullRoleDescription ? `${primaryDescription.slice(0, 500)}…` : primaryDescription;
239
+ return (
240
+ <div className={'text-sm'} style={{ color: 'var(--text-secondary)' }}>
241
+ <div className="flex items-center text-start gap-2 py-3">
242
+ <div className={'text-base font-semibold'} style={{ color: 'var(--text-main)' }}>{roleName}</div>
243
+ <span>-</span>
244
+ <div
245
+ className={
246
+ `px-3 py-1 inline-block rounded-md font-semibold shadow-sm text-[var(--text-main)] border`
247
+ }
248
+ style={{ borderColor: matchLabelToColor(em.label) }}
249
+ >
250
+ {em.label}
251
+ </div>
236
252
  </div>
237
- );
238
- })()}
239
- </div>
253
+ {primaryDescription ? (
254
+ <div>
255
+ <span>{visibleText}</span>
256
+ {isLong && (
257
+ <button
258
+ type="button"
259
+ onClick={() => setShowFullRoleDescription(v => !v)}
260
+ className={'ml-2 text-xs font-semibold'}
261
+ style={{ color: 'var(--text-main)' }}
262
+ >
263
+ {showFullRoleDescription ? 'See less' : 'See more'}
264
+ </button>
265
+ )}
266
+ </div>
267
+ ) : null}
268
+ </div>
269
+ );
270
+ })()}
240
271
  </Reveal>
241
272
  </div>
242
273
  )}
@@ -261,8 +292,8 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
261
292
  </div>
262
293
  </div>
263
294
  </div>
264
- )
265
- );
295
+ );
296
+ };
266
297
 
267
298
  const TechnicalSection = () => (
268
299
  <div className={`${wrapperMaxWidth} mx-auto`}>
@@ -428,7 +459,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
428
459
  })()}
429
460
  </div>
430
461
  )}
431
- <div id="appendix-connected">
462
+ <div>
432
463
  <ConnectedPlatforms accounts={connected} authenticity={assessmentResult?.account_authenticity} />
433
464
  </div>
434
465
  </div>
@@ -461,7 +492,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
461
492
  </style>
462
493
  {/* Long-form original layout retained for headless/print */}
463
494
  {OverviewSection()}
464
- {showRoleFit ? RoleFitSection() : null}
495
+ {showRoleFit ? <RoleFitSection /> : null}
465
496
  {TechnicalSection()}
466
497
  {RiskSection()}
467
498
  {AiSection()}
@@ -472,7 +503,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
472
503
  <TabNav />
473
504
  <div className={'px-2 sm:px-0 pb-2'}>
474
505
  {activeTab === 'overview' && OverviewSection()}
475
- {activeTab === 'role' && RoleFitSection()}
506
+ {activeTab === 'role' && <RoleFitSection />}
476
507
  {activeTab === 'technical' && TechnicalSection()}
477
508
  {activeTab === 'risk' && RiskSection()}
478
509
  {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
 
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import React from 'react';
3
+ import React, { useEffect } from 'react';
4
4
  import { formatLocalDate } from '../utils/date';
5
5
  import { FaGithub, FaGitlab, FaStackOverflow, FaLinkedin, FaGoogle, FaKaggle } from 'react-icons/fa';
6
6
  import { SiCredly, SiFiverr } from 'react-icons/si';
@@ -35,6 +35,26 @@ const formatProviderName = (name?: string) => {
35
35
 
36
36
  const ConnectedPlatforms = ({ accounts, authenticity }: { accounts?: ConnectedAccount[]; authenticity?: { label?: string; description?: string; oauth_connected?: string[]; mismatches?: Array<{ provider?: string; detail?: string }> } }) => {
37
37
  const list = Array.isArray(accounts) ? accounts : [];
38
+ useEffect(() => {
39
+ const flash = () => {
40
+ const hash = typeof window !== 'undefined' ? window.location.hash : '';
41
+ if (!hash) return;
42
+ const id = hash.startsWith('#') ? hash.slice(1) : hash;
43
+ const el = document.getElementById(id) as HTMLElement | null;
44
+ if (!el) return;
45
+ const originalBg = el.style.backgroundColor;
46
+ el.style.transition = 'background-color 300ms ease';
47
+ el.style.backgroundColor = 'rgba(2, 163, 137, 0.14)';
48
+ // Ensure visibility after tab switch
49
+ try { el.scrollIntoView({ behavior: 'smooth', block: 'start' }); } catch {}
50
+ setTimeout(() => {
51
+ el.style.backgroundColor = originalBg || '';
52
+ }, 1200);
53
+ };
54
+ flash();
55
+ window.addEventListener('hashchange', flash);
56
+ return () => window.removeEventListener('hashchange', flash);
57
+ }, []);
38
58
  if (list.length === 0) return null;
39
59
  return (
40
60
  <div className="pt-8">
@@ -42,12 +62,13 @@ const ConnectedPlatforms = ({ accounts, authenticity }: { accounts?: ConnectedAc
42
62
  const label = (authenticity?.label || '').toLowerCase();
43
63
  const isCritical = label === 'likely inauthentic';
44
64
  const isSuspicious = label === 'suspicious';
65
+ const displayLabel = isSuspicious ? 'Warning' : label
45
66
  if (!label) return null;
46
67
  return (
47
68
  <div className={"mb-4 rounded-md border p-3"} style={{ backgroundColor: 'var(--content-card-background)', borderColor: 'var(--icon-button-secondary)' }}>
48
69
  <div className="flex items-start gap-2">
49
70
  <span className="text-sm font-semibold" style={{ color: isCritical ? '#B00020' : isSuspicious ? '#9f580a' : 'var(--text-main)' }}>
50
- {(authenticity?.label) || ''}
71
+ {displayLabel}
51
72
  </span>
52
73
  </div>
53
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);
@@ -72,7 +73,7 @@ const ReportHeader = ({ badgeId, developerName, updatedAt, score = 0, badgeImage
72
73
  <div className={'font-semibold'} style={{ color: 'var(--text-main)' }}>Account Authenticity {isCritical ? 'Warning' : 'Notice'}</div>
73
74
  <div className={'mt-1'} style={{ color: 'var(--text-secondary)' }}>{accountAuthenticity?.description || (isCritical ? 'We detected signals that some linked accounts may be inauthentic. Review sources below.' : 'Some inconsistencies were observed across linked accounts. Review sources below.')}</div>
74
75
  <div className={'mt-2'}>
75
- <a href={'#appendix-connected'} className={'text-xs font-medium underline underline-offset-2'} style={{ color: 'var(--text-secondary)' }}>See more</a>
76
+ <a href="#appendix-connected" className={'text-xs font-medium underline underline-offset-2'} style={{ color: 'var(--text-secondary)' }}>See more</a>
76
77
  </div>
77
78
  </div>
78
79
  </div>
@@ -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
  }