kyd-shared-badge 0.3.128 → 0.3.130

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.128",
3
+ "version": "0.3.130",
4
4
  "private": false,
5
5
  "main": "./src/index.ts",
6
6
  "module": "./src/index.ts",
@@ -211,6 +211,28 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless, selfCheck = false,
211
211
  </div>
212
212
  );
213
213
 
214
+ const AuthenticityNotice = () => {
215
+ const label = (assessmentResult?.account_authenticity?.label || '').toLowerCase();
216
+ if (!label) return null;
217
+ const isCritical = label === 'likely inauthentic';
218
+ const isSuspicious = label === 'suspicious';
219
+ if (!isCritical && !isSuspicious) return null;
220
+ return (
221
+ <div className={'mt-4'}>
222
+ <div className={'rounded-md border p-3 flex items-start gap-2'} style={{ backgroundColor: 'var(--content-card-background)', borderColor: 'var(--icon-button-secondary)' }}>
223
+ <span className={'mt-0.5'} style={{ color: isCritical ? '#B00020' : '#9f580a' }}><FiAlertTriangle size={18} /></span>
224
+ <div className={'text-sm'}>
225
+ <div className={'font-semibold'} style={{ color: 'var(--text-main)' }}>{isCritical ? 'Warning' : 'Attention'}</div>
226
+ <div className={'mt-1'} style={{ color: 'var(--text-secondary)' }}>{assessmentResult?.account_authenticity?.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>
227
+ <div className={'mt-2'}>
228
+ <a href="#appendix-connected" className={'text-xs font-medium underline underline-offset-2'} style={{ color: 'var(--text-secondary)' }}>See more</a>
229
+ </div>
230
+ </div>
231
+ </div>
232
+ </div>
233
+ );
234
+ };
235
+
214
236
  const OverviewSection = () => (
215
237
  <div className={`${wrapperMaxWidth} mx-auto mt-6`}>
216
238
  <Reveal headless={isHeadless} offsetY={8} durationMs={500}>
@@ -387,7 +409,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless, selfCheck = false,
387
409
  </div>
388
410
  )}
389
411
  <div>
390
- <Reveal headless={isHeadless} as={'h4'} offsetY={8} className={'text-2xl font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>Score Improvement Recommendationsw</Reveal>
412
+ <Reveal headless={isHeadless} as={'h4'} offsetY={8} className={'text-2xl font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>Score Improvement Recommendations</Reveal>
391
413
  <Reveal headless={isHeadless}>
392
414
  <div className={'space-y-3'}>
393
415
  {rec?.summary ? (
@@ -631,6 +653,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless, selfCheck = false,
631
653
  }`}
632
654
  </style>
633
655
  {/* Long-form original layout retained for headless/print */}
656
+ <AuthenticityNotice />
634
657
  {OverviewSection()}
635
658
  <RoleFitSection />
636
659
  {TechnicalSection()}
@@ -643,6 +666,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless, selfCheck = false,
643
666
  <>
644
667
  <TabNav />
645
668
  <div className={'px-2 sm:px-0 pb-2'}>
669
+ {activeTab === 'overview' && <AuthenticityNotice />}
646
670
  {activeTab === 'overview' && OverviewSection()}
647
671
  {activeTab === 'technical' && TechnicalSection()}
648
672
  {activeTab === 'risk' && RiskSection()}
@@ -653,7 +677,10 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless, selfCheck = false,
653
677
  </div>
654
678
  </>
655
679
  ) : (
656
- OverviewSection()
680
+ <>
681
+ <AuthenticityNotice />
682
+ {OverviewSection()}
683
+ </>
657
684
  )
658
685
  )}
659
686
  {!headless && !suppressChat && (
@@ -9,6 +9,7 @@ import { red, yellow, green, hexToRgba } from '../colors';
9
9
  import RiskIcon from './icons/risk';
10
10
  import CodeIcon from './icons/code';
11
11
  import AiIcon from './icons/ai';
12
+ import { ProviderIcon } from '../utils/provider';
12
13
 
13
14
  interface SanctionSource {
14
15
  issuingEntity: string;
@@ -304,6 +305,7 @@ const AppendixTables: React.FC<AppendixTableProps> = ({ type, sources, searchedA
304
305
  <th
305
306
  key={header}
306
307
  scope="col"
308
+ title={header === 'Observation' ? 'Observation attribute details are part of the KYD proprietary framework and are available on a case-by-case basis.' : undefined}
307
309
  onClick={() => onHeaderClick(header)}
308
310
  className={'px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider'}
309
311
  style={{ color: 'var(--text-secondary)', cursor: isSortable ? 'pointer' : 'default', userSelect: isSortable ? 'none' : 'auto' }}
@@ -337,22 +339,6 @@ const AppendixTables: React.FC<AppendixTableProps> = ({ type, sources, searchedA
337
339
  }
338
340
  const br = source as BusinessRuleRow & { pillar?: string };
339
341
  const anchorId = br.uid ? `rule-${br.uid}` : undefined;
340
- const ProviderIcon = ({ name, pillar }: { name?: string, pillar?: string }) => {
341
- const n = (name || '').toLowerCase();
342
- if (n.includes('github')) return <FaGithub />;
343
- if (n.includes('gitlab')) return <FaGitlab />;
344
- if (n.includes('stack')) return <FaStackOverflow />;
345
- if (n.includes('credly')) return <SiCredly />;
346
- if (n.includes('fiverr')) return <SiFiverr />;
347
- if (n.includes('kaggle')) return <FaKaggle />;
348
- if (n.includes('google')) return <FaGoogle />;
349
- if (n.includes('linkedin')) return <FaLinkedin />;
350
- const p = (pillar || '').toLowerCase();
351
- if (p === 'risk') return <RiskIcon width={28} height={28} />;
352
- if (p === 'technical') return <CodeIcon width={28} height={28} />;
353
- if (p === 'ai') return <AiIcon width={28} height={28} />;
354
- return <span className="inline-block w-8 h-8 rounded-full" style={{ backgroundColor: 'var(--icon-button-secondary)' }} />;
355
- };
356
342
  return (
357
343
  <tr
358
344
  id={anchorId}
@@ -369,12 +355,18 @@ const AppendixTables: React.FC<AppendixTableProps> = ({ type, sources, searchedA
369
355
  >
370
356
  <td className={'px-4 py-4 whitespace-normal text-sm'} style={{ color: 'var(--text-secondary)' }}>
371
357
  <span title={br.provider || ''} className={'inline-flex items-center text-4xl'} style={{ color: 'var(--text-main)' }}>
372
- <ProviderIcon name={br.provider} pillar={br.pillar} />
358
+ <ProviderIcon name={br.provider} className='relative w-8 h-8' />
373
359
  </span>
374
360
  </td>
375
361
  <td className={'px-4 py-4 whitespace-normal text-sm'} style={{ color: 'var(--text-secondary)' }}>{br.pillar || '—'}</td>
376
362
  <td className={'px-4 py-4 whitespace-normal text-sm'} style={{ color: 'var(--text-secondary)' }}>{br.category || '—'}</td>
377
- <td className={'px-4 py-4 whitespace-normal text-sm font-medium'} style={{ color: 'var(--text-main)' }}>{br.label || '—'}</td>
363
+ <td
364
+ className={'px-4 py-4 whitespace-normal text-sm font-medium'}
365
+ style={{ color: 'var(--text-main)' }}
366
+ title={'Observation attribute details are part of the KYD proprietary framework and are available on a case-by-case basis.'}
367
+ >
368
+ -
369
+ </td>
378
370
  <td className={'px-4 py-4 whitespace-normal text-sm'} style={{ color: 'var(--text-secondary)' }}>{(() => {
379
371
  const weight = Number(br.weight);
380
372
  if (!Number.isFinite(weight)) return 'Neutral';
@@ -4,7 +4,7 @@ import Image from 'next/image';
4
4
  import { formatLocalDate } from '../utils/date';
5
5
  import countriesLib from 'i18n-iso-countries';
6
6
  import enLocale from 'i18n-iso-countries/langs/en.json';
7
- import { FiInfo, FiAlertTriangle } from 'react-icons/fi';
7
+ import { FiInfo } from 'react-icons/fi';
8
8
  import { ProviderIcon } from '../utils/provider';
9
9
  import { green1, green2, green3, green4, green5 } from '../colors';
10
10
 
@@ -80,25 +80,7 @@ const ReportHeader = ({ badgeId, developerName, updatedAt, score = 0, badgeImage
80
80
  className={'p-6 rounded-md border h-full'}
81
81
  style={{ backgroundColor: 'var(--content-card-background)', borderColor: 'var(--icon-button-secondary)', backgroundImage: `linear-gradient(${tint}, ${tint})` }}
82
82
  >
83
- {(() => {
84
- const label = (accountAuthenticity?.label || '').toLowerCase();
85
- if (!label) return null;
86
- const isCritical = label === 'likely inauthentic';
87
- const isSuspicious = label === 'suspicious';
88
- if (!isCritical && !isSuspicious) return null;
89
- return (
90
- <div className={'mb-4 rounded-md border p-3 flex items-start gap-2'} style={{ backgroundColor: 'var(--content-card-background)', borderColor: 'var(--icon-button-secondary)' }}>
91
- <span className={'mt-0.5'} style={{ color: isCritical ? '#B00020' : '#9f580a' }}><FiAlertTriangle size={18} /></span>
92
- <div className={'text-sm'}>
93
- <div className={'font-semibold'} style={{ color: 'var(--text-main)' }}>{isCritical ? 'Warning' : 'Attention'}</div>
94
- <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>
95
- <div className={'mt-2'}>
96
- <a href="#appendix-connected" className={'text-xs font-medium underline underline-offset-2'} style={{ color: 'var(--text-secondary)' }}>See more</a>
97
- </div>
98
- </div>
99
- </div>
100
- );
101
- })()}
83
+ {/* Authenticity notice moved to top-level display */}
102
84
  <div className={`flex gap-3 ${rightBadgeLayout ? 'flex-row items-center justify-between' : 'flex-col h-full'}`}>
103
85
  {/* Info section: Title, Candidate, Details and Summary */}
104
86
  <div className={`w-full ${rightBadgeLayout ? 'md:flex-1' : ''}`}>
@@ -172,7 +172,7 @@ export default function SkillsBubble({ skillsCategoryRadar, skillsByCategory, sk
172
172
  if (list.length > 10) {
173
173
  // Aggregate years and sources for remaining datapoints beyond the top 10
174
174
  const remaining = enriched.slice(10);
175
- const aggregatedYears = remaining.reduce((sum, it) => sum + Number(it.years || 0), 0);
175
+ const aggregatedYears = Math.round(remaining.reduce((sum, it) => sum + Number(it.years || 0), 0) * 10) / 10;
176
176
  const aggregatedSourcesSet = new Set<string>();
177
177
  for (const it of remaining) {
178
178
  if (Array.isArray(it.sources)) {
@@ -1,7 +1,10 @@
1
1
  'use client';
2
2
 
3
- import { FaGithub, FaGitlab, FaStackOverflow, FaLinkedin, FaGoogle, FaKaggle } from 'react-icons/fa';
4
- import { SiCoursera, SiCredly, SiFiverr, SiToptal, SiUdemy } from 'react-icons/si';
3
+ import { FaGithub, FaGitlab, FaStackOverflow, FaLinkedin, FaGoogle, FaKaggle, FaShieldAlt } from 'react-icons/fa';
4
+ import { SiCoursera, SiCredly, SiFiverr, SiUdemy, SiAmazon } from 'react-icons/si';
5
+ import RiskIcon from '../components/icons/risk';
6
+ import CodeIcon from '../components/icons/code';
7
+ import AiIcon from '../components/icons/ai';
5
8
 
6
9
  export const ProviderIcon = ({ name, className }: { name?: string; className?: string }) => {
7
10
  const n = (name || '').toLowerCase();
@@ -11,11 +14,15 @@ export const ProviderIcon = ({ name, className }: { name?: string; className?: s
11
14
  if (n.includes('credly')) return <SiCredly className={className} />;
12
15
  if (n.includes('fiverr')) return <SiFiverr className={className} />;
13
16
  if (n.includes('kaggle')) return <FaKaggle className={className} />;
14
- if (n.includes('google')) return <FaGoogle className={className} />;
17
+ if (n.includes('google') || n.includes('gscholar')) return <FaGoogle className={className} />;
15
18
  if (n.includes('linkedin')) return <FaLinkedin className={className} />;
16
- if (n.includes('toptal')) return <SiToptal className={className} />;
17
19
  if (n.includes('coursera')) return <SiCoursera className={className} />;
18
20
  if (n.includes('udemy')) return <SiUdemy className={className} />;
21
+ if (n.includes('amazon')) return <SiAmazon className={className} />;
22
+ if (n.includes('cyber')) return <RiskIcon className={className} />;
23
+ if (n.includes('risk')) return <RiskIcon className={className} />;
24
+ if (n.includes('technical')) return <CodeIcon className={className} />;
25
+ if (n.includes('ai')) return <AiIcon className={className} />;
19
26
  return <span className={className || 'inline-block w-3 h-3 rounded-full'} style={{ backgroundColor: 'var(--icon-button-secondary)' }} />;
20
27
  };
21
28
 
@@ -29,9 +36,9 @@ export const getProviderDisplayName = (name?: string): string => {
29
36
  if (n.includes('kaggle')) return 'Kaggle';
30
37
  if (n.includes('google')) return 'Google Scholar';
31
38
  if (n.includes('linkedin')) return 'LinkedIn';
32
- if (n.includes('toptal')) return 'TopTal';
33
39
  if (n.includes('coursera')) return 'Coursera';
34
40
  if (n.includes('udemy')) return 'Udemy';
41
+ if (n.includes('amazon')) return 'Amazon Author';
35
42
  return name || 'Provider';
36
43
  };
37
44
 
@@ -45,9 +52,9 @@ export const getProviderTooltipCopy = (provider?: string): string => {
45
52
  if (n.includes('kaggle')) return 'Competition results, notebooks, and dataset contributions that reflect analytical skill.';
46
53
  if (n.includes('google')) return 'Publications, citations, and scholarly presence indicating research impact.';
47
54
  if (n.includes('linkedin')) return 'Professional history, endorsements, and network signals indicating credibility.';
48
- if (n.includes('toptal')) return 'Professional history, endorsements, and network signals indicating credibility.';
49
55
  if (n.includes('coursera')) return 'Professional history, training, and course completion signals indicating credibility.';
50
56
  if (n.includes('udemy')) return 'Professional history, training, and course completion signals indicating credibility.';
57
+ if (n.includes('amazon')) return 'Published works and author profile signals from Amazon Stores indicating credibility.';
51
58
  return 'Signals contributed from this provider relevant to capability and trust.';
52
59
  };
53
60