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
|
@@ -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
|
-
<
|
|
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-')
|
|
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
|
-
|
|
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
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
{
|
|
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=
|
|
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:
|
|
176
|
+
label: 'Incompatible' | 'Weak Match' | 'Moderate Match' | 'Strong Match' | 'Optimal Match';
|
|
176
177
|
description: string;
|
|
177
178
|
coaching?: EnterpriseCoaching;
|
|
178
179
|
}
|