kyd-shared-badge 0.3.140 → 0.3.200

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.140",
3
+ "version": "0.3.200",
4
4
  "private": false,
5
5
  "main": "./src/index.ts",
6
6
  "module": "./src/index.ts",
@@ -139,7 +139,8 @@ 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
- sourcesProviders={(badgeData?.connectedAccounts || []).map(a => (a?.name || '').toLowerCase())}
142
+ sourcesProviders={connected.map(a => (a?.name || '').toLowerCase())}
143
+ connectedAccounts={connected}
143
144
  rightBadgeLayout={!hasEnterpriseMatch}
144
145
  />
145
146
  </div>
@@ -121,7 +121,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless, selfCheck = false,
121
121
  { key: 'risk', label: 'KYD Risk' },
122
122
  // { key: 'ai', label: 'KYD AI' },
123
123
  { key: 'role', label: showRoleFit ? 'Role Fit & Coaching' : 'Coaching' },
124
- { key: 'resume', label: 'Resume' },
124
+ { key: 'resume', label: 'KYD Resume' },
125
125
  { key: 'appendix', label: 'Appendix' }
126
126
  ];
127
127
  return arr;
@@ -245,14 +245,15 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless, selfCheck = false,
245
245
  developerName={badgeData.developerName}
246
246
  updatedAt={updatedAt}
247
247
  score={overallFinalPercent || 0}
248
- isPublic={true}
248
+ isPublic={isPublicView}
249
249
  badgeImageUrl={badgeData.badgeImageUrl || ''}
250
250
  summary={undefined}
251
251
  enterpriseMatch={null}
252
252
  countries={(assessmentResult?.screening_sources?.ip_risk_analysis?.raw_data?.countries) || []}
253
253
  accountAuthenticity={assessmentResult?.account_authenticity}
254
254
  companyName={badgeData.companyName}
255
- sourcesProviders={(badgeData?.connectedAccounts || []).map(a => (a?.name || '').toLowerCase())}
255
+ sourcesProviders={connected.map(a => (a?.name || '').toLowerCase())}
256
+ connectedAccounts={connected}
256
257
  selfCheck={selfCheck}
257
258
  rightBadgeLayout={!hasEnterpriseMatch}
258
259
  />
@@ -340,54 +341,62 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless, selfCheck = false,
340
341
  <div className="space-y-10">
341
342
  {hasEnterpriseMatch && (
342
343
  <div>
343
- <Reveal headless={isHeadless} as={'h4'} offsetY={8} className={'text-2xl font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>Role Alignment</Reveal>
344
- <Reveal headless={isHeadless}>
345
- {(() => {
346
- const em = assessmentResult?.enterprise_match;
347
- if (!em) return null;
348
- const role = em.role || {};
349
- const roleName = role?.name || '';
350
- const primaryDescription = (role?.description || em.description || '').trim();
351
- const isLong = primaryDescription.length > 500;
352
- const visibleText = isLong && !showFullRoleDescription ? `${primaryDescription.slice(0, 500)}…` : primaryDescription;
353
- return (
354
- <div className='mt-3' style={{ color: 'var(--text-secondary)' }}>
355
- <div className={'text-base font-semibold'} style={{ color: 'var(--text-main)' }}>{roleName}</div>
356
- {primaryDescription ? (
357
- <div>
358
- <span>{visibleText}</span>
359
- {isLong && (
360
- <button
361
- type="button"
362
- onClick={() => setShowFullRoleDescription(v => !v)}
363
- className={'ml-2 text-xs font-semibold'}
364
- style={{ color: 'var(--text-main)' }}
365
- >
366
- {showFullRoleDescription ? 'See less' : 'See more'}
367
- </button>
368
- )}
369
- </div>
370
- ) : null}
371
-
372
- {em.description ? (
373
- <div>
374
- <div className="flex items-center text-start gap-2 mt-3">
375
- <div className={'text-base font-semibold'} style={{ color: 'var(--text-main)' }}>Alignment Description</div>
376
- <span>-</span>
377
- <div
378
- className={`px-2 py-1 inline-block rounded-md font-semibold shadow-sm text-[var(--text-main)] border`}
379
- style={{ borderColor: matchLabelToColor(em.label) }}
380
- >
381
- {em.label}
382
- </div>
344
+ {(() => {
345
+ const em = assessmentResult?.enterprise_match;
346
+ if (!em) return null;
347
+ const role = em.role || {};
348
+ const roleName = role?.name || '';
349
+ return (
350
+ <>
351
+ <Reveal headless={isHeadless} as={'h4'} offsetY={8} className={'text-2xl font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>
352
+ Role Alignment{roleName ? ` ${roleName}` : ''}
353
+ </Reveal>
354
+ <Reveal headless={isHeadless}>
355
+ {(() => {
356
+ const primaryDescription = (role?.description || em.description || '').trim();
357
+ const isLong = primaryDescription.length > 500;
358
+ const visibleText = isLong && !showFullRoleDescription ? `${primaryDescription.slice(0, 500)}…` : primaryDescription;
359
+ return (
360
+ <div className='mt-3' style={{ color: 'var(--text-secondary)' }}>
361
+ <div className={'text-base font-semibold'} style={{ color: 'var(--text-main)' }}>{roleName}</div>
362
+ {primaryDescription ? (
363
+ <div>
364
+ <span>{visibleText}</span>
365
+ {isLong && (
366
+ <button
367
+ type="button"
368
+ onClick={() => setShowFullRoleDescription(v => !v)}
369
+ className={'ml-2 text-xs font-semibold'}
370
+ style={{ color: 'var(--text-main)' }}
371
+ >
372
+ {showFullRoleDescription ? 'See less' : 'See more'}
373
+ </button>
374
+ )}
375
+ </div>
376
+ ) : null}
377
+
378
+ {em.description ? (
379
+ <div>
380
+ <div className="flex items-center text-start gap-2 mt-3">
381
+ <div className={'text-base font-semibold'} style={{ color: 'var(--text-main)' }}>Alignment Description</div>
382
+ <span>-</span>
383
+ <div
384
+ className={`px-2 py-1 inline-block rounded-md font-semibold shadow-sm text-[var(--text-main)] border`}
385
+ style={{ borderColor: matchLabelToColor(em.label) }}
386
+ >
387
+ {em.label}
388
+ </div>
389
+ </div>
390
+ <div className={'text-sm'} style={{ color: 'var(--text-secondary)' }}>{em.description}</div>
391
+ </div>
392
+ ) : null}
383
393
  </div>
384
- <div className={'text-sm'} style={{ color: 'var(--text-secondary)' }}>{em.description}</div>
385
- </div>
386
- ) : null}
387
- </div>
388
- );
389
- })()}
390
- </Reveal>
394
+ );
395
+ })()}
396
+ </Reveal>
397
+ </>
398
+ );
399
+ })()}
391
400
  </div>
392
401
  )}
393
402
  {hasCoaching && (() => {
@@ -36,8 +36,8 @@ export default function ChatWidget({ api = '/api/chat', title = 'KYD Bot', hintT
36
36
  window.addEventListener('resize', check);
37
37
  return () => window.removeEventListener('resize', check);
38
38
  }, []);
39
- // Sidebar open state (default expanded on desktop, collapsed on mobile)
40
- const [open, setOpen] = useState<boolean>(true);
39
+ // Sidebar open state (default collapsed)
40
+ const [open, setOpen] = useState<boolean>(false);
41
41
  // Avoid hydration mismatches by rendering only after mount
42
42
  const [hasMounted, setHasMounted] = useState(false);
43
43
  useEffect(() => { setHasMounted(true); }, []);
@@ -6,6 +6,7 @@ import countriesLib from 'i18n-iso-countries';
6
6
  import enLocale from 'i18n-iso-countries/langs/en.json';
7
7
  import { FiInfo } from 'react-icons/fi';
8
8
  import { ProviderIcon } from '../utils/provider';
9
+ import type { ConnectedAccount } from '../types';
9
10
  import { green1, green2, green3, green4, green5 } from '../colors';
10
11
 
11
12
  // Register English locale once at module import time
@@ -56,11 +57,14 @@ interface ReportHeaderProps {
56
57
  accountAuthenticity?: { label?: string; description?: string };
57
58
  companyName?: string;
58
59
  sourcesProviders?: string[];
60
+ connectedAccounts?: ConnectedAccount[];
59
61
  selfCheck?: boolean;
60
62
  rightBadgeLayout?: boolean;
61
63
  }
62
64
 
63
- const ReportHeader = ({ badgeId, developerName, updatedAt, score = 0, badgeImageUrl, summary, enterpriseMatch, countries = [], accountAuthenticity, companyName, sourcesProviders = [], selfCheck, rightBadgeLayout }: ReportHeaderProps) => {
65
+ const normalizeProvider = (value?: string) => (value || '').toLowerCase().trim();
66
+
67
+ const ReportHeader = ({ badgeId, developerName, updatedAt, score = 0, badgeImageUrl, summary, enterpriseMatch, countries = [], accountAuthenticity, companyName, sourcesProviders = [], connectedAccounts = [], selfCheck, rightBadgeLayout, isPublic }: ReportHeaderProps) => {
64
68
  // Use the dynamic image if available, otherwise fall back to the score-based one.
65
69
  const finalBadgeImageUrl = badgeImageUrl || getBadgeImageUrl(score || 0, false, selfCheck);
66
70
  const tint = hexToRgba(pickTint(score || 0, selfCheck), 0.06);
@@ -79,6 +83,35 @@ const ReportHeader = ({ badgeId, developerName, updatedAt, score = 0, badgeImage
79
83
  return true;
80
84
  });
81
85
  })();
86
+ const sourceAccounts = (() => {
87
+ const map: Record<string, ConnectedAccount> = {};
88
+ (Array.isArray(connectedAccounts) ? connectedAccounts : []).forEach((account) => {
89
+ const normalized = normalizeProvider(account?.name);
90
+ if (normalized && !map[normalized]) map[normalized] = account;
91
+ const underscored = normalized.replace(/\s+/g, '_');
92
+ if (underscored && !map[underscored]) map[underscored] = account;
93
+ const spaced = normalized.replace(/_/g, ' ');
94
+ if (spaced && !map[spaced]) map[spaced] = account;
95
+ });
96
+ return map;
97
+ })();
98
+ const resolveSourceLink = (provider: string) => {
99
+ if (!provider) return null;
100
+ if (!isPublic) {
101
+ return { href: '#appendix-connected', external: false };
102
+ }
103
+ const normalized = normalizeProvider(provider);
104
+ const account = sourceAccounts[normalized] || sourceAccounts[normalized.replace(/\s+/g, '_')] || sourceAccounts[normalized.replace(/_/g, ' ')];
105
+ if (account?.url) {
106
+ return { href: account.url, external: true, providerName: account?.name || provider };
107
+ }
108
+ return null;
109
+ };
110
+ const SourceIcon = ({ provider }: { provider: string }) => (
111
+ <span className={'text-sm'} style={{ color: 'var(--text-main)' }}>
112
+ <ProviderIcon name={provider} className={'inline-block align-middle'} />
113
+ </span>
114
+ );
82
115
 
83
116
  return (
84
117
  <div
@@ -126,18 +159,48 @@ const ReportHeader = ({ badgeId, developerName, updatedAt, score = 0, badgeImage
126
159
  );
127
160
  })()}
128
161
  {sources.length > 0 && (
129
- <p className='flex items-center gap-2 mt-2 text-sm'>
130
- <a href="#appendix-connected" className={'inline-flex items-center gap-2 group'} style={{ color: 'var(--text-secondary)' }}>
162
+ isPublic ? (
163
+ <div className='flex flex-wrap items-center gap-2 mt-2 text-sm' style={{ color: 'var(--text-secondary)' }}>
131
164
  <span className={'font-semibold'}>Sources Shared:</span>
132
- <span className={'flex items-center gap-2'}>
133
- {sources.map((provider, idx) => (
134
- <span key={`${provider}-${idx}`} className={'text-sm'} style={{ color: 'var(--text-main)' }}>
135
- <ProviderIcon name={provider} className={'inline-block align-middle'} />
136
- </span>
137
- ))}
165
+ <span className={'flex items-center gap-2 flex-wrap'}>
166
+ {sources.map((provider, idx) => {
167
+ const link = resolveSourceLink(provider);
168
+ const icon = <SourceIcon provider={provider} />;
169
+ if (link) {
170
+ return (
171
+ <a
172
+ key={`${provider}-${idx}`}
173
+ href={link.href}
174
+ target={link.external ? '_blank' : undefined}
175
+ rel={link.external ? 'noreferrer noopener' : undefined}
176
+ className={'inline-flex items-center'}
177
+ style={{ color: 'var(--text-main)' }}
178
+ title={link.providerName || provider}
179
+ >
180
+ {icon}
181
+ </a>
182
+ );
183
+ }
184
+ return (
185
+ <span key={`${provider}-${idx}`} className={'inline-flex items-center'}>
186
+ {icon}
187
+ </span>
188
+ );
189
+ })}
138
190
  </span>
139
- </a>
140
- </p>
191
+ </div>
192
+ ) : (
193
+ <p className='flex items-center gap-2 mt-2 text-sm'>
194
+ <a href="#appendix-connected" className={'inline-flex items-center gap-2 group'} style={{ color: 'var(--text-secondary)' }}>
195
+ <span className={'font-semibold'}>Sources Shared:</span>
196
+ <span className={'flex items-center gap-2'}>
197
+ {sources.map((provider, idx) => (
198
+ <SourceIcon key={`${provider}-${idx}`} provider={provider} />
199
+ ))}
200
+ </span>
201
+ </a>
202
+ </p>
203
+ )
141
204
  )}
142
205
  {(enterpriseMatch?.description || matchLabel) ? (
143
206
  <div className={'hidden md:block text-sm space-y-2 pt-4'} style={{ borderTop: '1px solid var(--icon-button-secondary)' }}>