kyd-shared-badge 0.2.2 → 0.2.3
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 +1 -1
- package/src/SharedBadgeDisplay.tsx +27 -44
- package/src/components/AppendixTables.tsx +30 -33
- package/src/components/IpRiskAnalysisDisplay.tsx +13 -13
- package/src/components/ProviderInsights.tsx +15 -20
- package/src/components/ReportHeader.tsx +13 -19
- package/src/components/ShareButton.tsx +10 -7
package/package.json
CHANGED
|
@@ -92,7 +92,7 @@ const ScoreCard = ({ title, score, description, descriptor, scoreType, isRecruit
|
|
|
92
92
|
);
|
|
93
93
|
};
|
|
94
94
|
|
|
95
|
-
const SharedBadgeDisplay = ({ badgeData
|
|
95
|
+
const SharedBadgeDisplay = ({ badgeData }: { badgeData: PublicBadgeData }) => {
|
|
96
96
|
const { badgeId, developerName, assessmentResult, updatedAt, connectedPlatforms } = badgeData;
|
|
97
97
|
const { summary_scores, report_summary, developer_trust_explanation, key_skills, screening_sources, industry_considerations } = assessmentResult;
|
|
98
98
|
|
|
@@ -100,8 +100,7 @@ const SharedBadgeDisplay = ({ badgeData, type = 'individual' }: { badgeData: Pub
|
|
|
100
100
|
const riskScore = summary_scores.risk_score;
|
|
101
101
|
const aiUsageScore = summary_scores.ai_usage;
|
|
102
102
|
|
|
103
|
-
const wrapperMaxWidth =
|
|
104
|
-
const isRecruiter = type === 'recruiter';
|
|
103
|
+
const wrapperMaxWidth = 'max-w-5xl';
|
|
105
104
|
|
|
106
105
|
return (
|
|
107
106
|
<div className={`${wrapperMaxWidth} mx-auto`}>
|
|
@@ -111,11 +110,7 @@ const SharedBadgeDisplay = ({ badgeData, type = 'individual' }: { badgeData: Pub
|
|
|
111
110
|
shareTitle={`KYD Self-Check™ Report | ${badgeData.developerName}`}
|
|
112
111
|
shareText="Check out my KYD Self-Check™ from Know Your Developer™"
|
|
113
112
|
buttonText="Share"
|
|
114
|
-
className={
|
|
115
|
-
isRecruiter
|
|
116
|
-
? 'flex items-center justify-center h-10 w-10 sm:w-auto sm:px-4 sm:py-2 text-white rounded-full sm:rounded-md transition-all duration-300 ease-in-out bg-[var(--icon-accent)] hover:bg-[var(--icon-accent-hover)]'
|
|
117
|
-
: 'flex items-center justify-center h-10 w-10 sm:w-auto sm:px-4 sm:py-2 bg-indigo-600 text-white rounded-full sm:rounded-md hover:bg-indigo-700 transition-all duration-300 ease-in-out'
|
|
118
|
-
}
|
|
113
|
+
className={'flex items-center justify-center h-10 w-10 sm:w-auto sm:px-4 sm:py-2 text-white rounded-full sm:rounded-md transition-all duration-300 ease-in-out bg-[var(--icon-accent)] hover:bg-[var(--icon-accent-hover)]'}
|
|
119
114
|
/>
|
|
120
115
|
</div>
|
|
121
116
|
<ReportHeader
|
|
@@ -125,15 +120,10 @@ const SharedBadgeDisplay = ({ badgeData, type = 'individual' }: { badgeData: Pub
|
|
|
125
120
|
score={summary_scores.kyd_self_check.score}
|
|
126
121
|
isPublic={true}
|
|
127
122
|
badgeImageUrl={badgeData.badgeImageUrl || ''}
|
|
128
|
-
type={type}
|
|
129
123
|
/>
|
|
130
124
|
<div
|
|
131
|
-
className={
|
|
132
|
-
|
|
133
|
-
? 'rounded-xl shadow-xl p-6 sm:p-8 mt-8 border'
|
|
134
|
-
: 'bg-white/80 dark:bg-neutral-900/80 backdrop-blur-sm rounded-lg shadow-xl p-6 sm:p-8 mt-8'
|
|
135
|
-
}
|
|
136
|
-
style={isRecruiter ? { backgroundColor: 'var(--content-card-background)', borderColor: 'var(--icon-button-secondary)' } : undefined}
|
|
125
|
+
className={'rounded-xl shadow-xl p-6 sm:p-8 mt-8 border'}
|
|
126
|
+
style={{ backgroundColor: 'var(--content-card-background)', borderColor: 'var(--icon-button-secondary)' }}
|
|
137
127
|
>
|
|
138
128
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-12">
|
|
139
129
|
<ScoreCard
|
|
@@ -141,7 +131,6 @@ const SharedBadgeDisplay = ({ badgeData, type = 'individual' }: { badgeData: Pub
|
|
|
141
131
|
score={devTrustScore?.score || 0}
|
|
142
132
|
description={devTrustScore?.description || ''}
|
|
143
133
|
scoreType='number'
|
|
144
|
-
isRecruiter={isRecruiter}
|
|
145
134
|
iconImageSrc={getTechnicalIconSrc(devTrustScore?.score || 0)}
|
|
146
135
|
/>
|
|
147
136
|
<ScoreCard
|
|
@@ -149,7 +138,6 @@ const SharedBadgeDisplay = ({ badgeData, type = 'individual' }: { badgeData: Pub
|
|
|
149
138
|
score={riskScore?.score || 0}
|
|
150
139
|
description={riskScore?.description || ''}
|
|
151
140
|
scoreType='risk'
|
|
152
|
-
isRecruiter={isRecruiter}
|
|
153
141
|
iconImageSrc={getRiskIconSrc(riskScore?.score || 0)}
|
|
154
142
|
/>
|
|
155
143
|
<ScoreCard
|
|
@@ -158,23 +146,22 @@ const SharedBadgeDisplay = ({ badgeData, type = 'individual' }: { badgeData: Pub
|
|
|
158
146
|
description={aiUsageScore?.description || 'Analysis of AI tool usage and transparency.'}
|
|
159
147
|
descriptor={aiUsageScore?.descriptor}
|
|
160
148
|
scoreType='descriptor'
|
|
161
|
-
isRecruiter={isRecruiter}
|
|
162
149
|
iconImageSrc={getAiIconSrc(aiUsageScore?.descriptor)}
|
|
163
150
|
/>
|
|
164
151
|
</div>
|
|
165
152
|
|
|
166
|
-
<div className={
|
|
153
|
+
<div className={'space-y-12 divide-y'} style={{ borderColor: 'var(--icon-button-secondary)' }}>
|
|
167
154
|
<div className="pt-8 first:pt-0">
|
|
168
|
-
<h3 className={
|
|
169
|
-
<div className={
|
|
155
|
+
<h3 className={'text-2xl font-bold mb-4'} style={{ color: 'var(--text-main)' }}>1. Summary Findings</h3>
|
|
156
|
+
<div className={'prose prose-sm max-w-none'} style={{ color: 'var(--text-secondary)' }}>
|
|
170
157
|
<p>{report_summary}</p>
|
|
171
158
|
</div>
|
|
172
159
|
{key_skills && key_skills.length > 0 && (
|
|
173
160
|
<div className="mt-6">
|
|
174
|
-
<h4 className={
|
|
161
|
+
<h4 className={'text-lg font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>Key Skills Observed</h4>
|
|
175
162
|
<div className="flex flex-wrap gap-2">
|
|
176
163
|
{key_skills.map((skill: string, index: number) => (
|
|
177
|
-
<span key={index} className={
|
|
164
|
+
<span key={index} className={'text-xs font-medium px-2.5 py-1 rounded-full'} style={{ backgroundColor: 'var(--icon-button-secondary)', color: 'var(--text-main)' }}>
|
|
178
165
|
{skill}
|
|
179
166
|
</span>
|
|
180
167
|
))}
|
|
@@ -184,29 +171,27 @@ const SharedBadgeDisplay = ({ badgeData, type = 'individual' }: { badgeData: Pub
|
|
|
184
171
|
</div>
|
|
185
172
|
|
|
186
173
|
<div className="pt-8">
|
|
187
|
-
<h3 className={
|
|
188
|
-
<div className={
|
|
174
|
+
<h3 className={'text-xl font-bold mb-3'} style={{ color: 'var(--text-main)' }}>2. KYD Technical™ Signals</h3>
|
|
175
|
+
<div className={'prose prose-sm max-w-none mb-6'} style={{ color: 'var(--text-secondary)' }}>
|
|
189
176
|
<p>{developer_trust_explanation}</p>
|
|
190
177
|
</div>
|
|
191
178
|
<ProviderInsights
|
|
192
179
|
platforms={connectedPlatforms || []}
|
|
193
180
|
insights={assessmentResult.provider_insights}
|
|
194
|
-
variant="public"
|
|
195
|
-
view={type}
|
|
196
181
|
/>
|
|
197
182
|
</div>
|
|
198
183
|
|
|
199
184
|
<div className="pt-8">
|
|
200
|
-
<h3 className={
|
|
185
|
+
<h3 className={'text-xl font-bold mb-3'} style={{ color: 'var(--text-main)' }}>3. KYD Risk™ Signals</h3>
|
|
201
186
|
{badgeData.optOutScreening ? (
|
|
202
|
-
<div className={
|
|
187
|
+
<div className={'mb-4 p-4 rounded-lg border'} style={{ backgroundColor: 'var(--icon-button-secondary)', borderColor: 'var(--icon-button-secondary)' }}>
|
|
203
188
|
<div className="flex items-start">
|
|
204
189
|
<span className="h-5 w-5 mr-3 mt-0.5 flex-shrink-0" style={{ color: yellow }}>
|
|
205
190
|
<FiAlertTriangle size={20} />
|
|
206
191
|
</span>
|
|
207
192
|
<div>
|
|
208
|
-
<h4 className={'font-bold'} style={
|
|
209
|
-
<p className={'text-sm mt-1'} style={
|
|
193
|
+
<h4 className={'font-bold'} style={{ color: 'var(--text-main)' }}>User Opted Out of Screening</h4>
|
|
194
|
+
<p className={'text-sm mt-1'} style={{ color: 'var(--text-secondary)' }}>
|
|
210
195
|
The user chose not to participate in the automated sanctions and risk screening process. The risk score reflects this decision and is for informational purposes only.
|
|
211
196
|
</p>
|
|
212
197
|
</div>
|
|
@@ -214,7 +199,7 @@ const SharedBadgeDisplay = ({ badgeData, type = 'individual' }: { badgeData: Pub
|
|
|
214
199
|
</div>
|
|
215
200
|
) : (
|
|
216
201
|
<>
|
|
217
|
-
<div className={
|
|
202
|
+
<div className={'prose prose-sm max-w-none space-y-4 mb-6'} style={{ color: 'var(--text-secondary)' }}>
|
|
218
203
|
<p>{riskScore?.description || ''}</p>
|
|
219
204
|
</div>
|
|
220
205
|
<IpRiskAnalysisDisplay ipRiskAnalysis={screening_sources?.ip_risk_analysis} />
|
|
@@ -223,8 +208,8 @@ const SharedBadgeDisplay = ({ badgeData, type = 'individual' }: { badgeData: Pub
|
|
|
223
208
|
</div>
|
|
224
209
|
|
|
225
210
|
<div className="pt-8">
|
|
226
|
-
<h3 className={
|
|
227
|
-
<div className={
|
|
211
|
+
<h3 className={'text-xl font-bold mb-3'} style={{ color: 'var(--text-main)' }}>4. KYD AI™ Signals <span className="text-sm font-semibold bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200 px-2 py-1 rounded-full align-middle">Beta</span></h3>
|
|
212
|
+
<div className={'prose prose-sm max-w-none mb-6 space-y-4'} style={{ color: 'var(--text-secondary)' }}>
|
|
228
213
|
<p>{assessmentResult.ai_usage_summary?.explanation}</p>
|
|
229
214
|
{assessmentResult.ai_usage_summary?.key_findings && (
|
|
230
215
|
<ul className="list-disc list-inside">
|
|
@@ -237,41 +222,39 @@ const SharedBadgeDisplay = ({ badgeData, type = 'individual' }: { badgeData: Pub
|
|
|
237
222
|
</div>
|
|
238
223
|
|
|
239
224
|
<div className="pt-8">
|
|
240
|
-
<h3 className={
|
|
241
|
-
<div className={
|
|
225
|
+
<h3 className={'text-xl font-bold mb-3'} style={{ color: 'var(--text-main)' }}>5. Industry Considerations</h3>
|
|
226
|
+
<div className={'prose prose-sm max-w-none'} style={{ color: 'var(--text-secondary)' }}>
|
|
242
227
|
<p>{industry_considerations}</p>
|
|
243
228
|
</div>
|
|
244
229
|
</div>
|
|
245
230
|
|
|
246
231
|
{!badgeData.optOutScreening && screening_sources && (
|
|
247
232
|
<div className="pt-8">
|
|
248
|
-
<h3 className={
|
|
233
|
+
<h3 className={'text-2xl font-bold mb-4'} style={{ color: 'var(--text-main)' }}>6. Appendix: Data Sources</h3>
|
|
249
234
|
<div className="space-y-8">
|
|
250
235
|
<div>
|
|
251
|
-
<h4 className={
|
|
236
|
+
<h4 className={'text-xl font-bold mb-4'} style={{ color: 'var(--text-main)' }}>Sanctions & Watchlists</h4>
|
|
252
237
|
<AppendixTables
|
|
253
238
|
type="sanctions"
|
|
254
239
|
sources={[...(screening_sources.ofac_lists || []), ...(screening_sources.additional_watchlists || [])]}
|
|
255
240
|
searchedAt={updatedAt}
|
|
256
241
|
developerName={developerName || 'this developer'}
|
|
257
|
-
view={type}
|
|
258
242
|
/>
|
|
259
243
|
</div>
|
|
260
244
|
<div>
|
|
261
|
-
<h4 className={
|
|
245
|
+
<h4 className={'text-xl font-bold mb-4'} style={{ color: 'var(--text-main)' }}>Country-specific Entity Affiliations</h4>
|
|
262
246
|
<AppendixTables
|
|
263
247
|
type="domains"
|
|
264
248
|
sources={screening_sources.risk_profile_domains || []}
|
|
265
249
|
searchedAt={updatedAt}
|
|
266
250
|
developerName={developerName || 'this developer'}
|
|
267
|
-
view={type}
|
|
268
251
|
/>
|
|
269
252
|
</div>
|
|
270
253
|
</div>
|
|
271
254
|
</div>
|
|
272
255
|
)}
|
|
273
256
|
|
|
274
|
-
<div className={
|
|
257
|
+
<div className={'pt-8 text-sm text-center'} style={{ color: 'var(--text-secondary)' }}>
|
|
275
258
|
Report Completed: {new Date(updatedAt).toLocaleString(undefined, {
|
|
276
259
|
year: 'numeric',
|
|
277
260
|
month: 'long',
|
|
@@ -283,8 +266,8 @@ const SharedBadgeDisplay = ({ badgeData, type = 'individual' }: { badgeData: Pub
|
|
|
283
266
|
</div>
|
|
284
267
|
</div>
|
|
285
268
|
</div>
|
|
286
|
-
<footer className={
|
|
287
|
-
<p className={
|
|
269
|
+
<footer className={'mt-12 pt-6 border-t'} style={{ borderColor: 'var(--icon-button-secondary)' }}>
|
|
270
|
+
<p className={'text-center text-xs max-w-4xl mx-auto'} style={{ color: 'var(--text-secondary)' }}>
|
|
288
271
|
© 2025 Know Your Developer, LLC. All rights reserved. KYD Self-Check™, and associated marks are trademarks of Know Your Developer, LLC. This document is confidential, proprietary, and intended solely for the individual or entity to whom it is addressed. Unauthorized use, disclosure, copying, or distribution of this document or any of its contents is strictly prohibited and may be unlawful. Know Your Developer, LLC assumes no responsibility or liability for any errors or omissions contained herein. Report validity subject to the terms and conditions stated on the official Know Your Developer website located at https://knowyourdeveloper.ai.
|
|
289
272
|
</p>
|
|
290
273
|
</footer>
|
|
@@ -22,54 +22,51 @@ interface AppendixTableProps {
|
|
|
22
22
|
sources: string[] | DomainCSVRow[];
|
|
23
23
|
searchedAt: string;
|
|
24
24
|
developerName: string;
|
|
25
|
-
view?: 'recruiter' | 'individual';
|
|
26
25
|
}
|
|
27
26
|
|
|
28
|
-
const SanctionsRow = ({ source, searchedAt, developerName
|
|
29
|
-
<tr className={
|
|
30
|
-
<td className={
|
|
27
|
+
const SanctionsRow = ({ source, searchedAt, developerName }: { source: SanctionSource, searchedAt: string, developerName: string }) => (
|
|
28
|
+
<tr className={'transition-colors hover:bg-black/5'}>
|
|
29
|
+
<td className={'px-4 py-4 whitespace-nowrap text-sm font-medium'} style={{ color: 'var(--text-main)' }}>
|
|
31
30
|
{source.issuingEntity}
|
|
32
31
|
</td>
|
|
33
|
-
<td className={
|
|
32
|
+
<td className={'px-4 py-4 whitespace-normal text-sm'} style={{ color: 'var(--text-secondary)' }}>
|
|
34
33
|
{source.listName}
|
|
35
34
|
</td>
|
|
36
|
-
<td className={
|
|
35
|
+
<td className={'px-4 py-4 whitespace-nowrap text-sm'} style={{ color: 'var(--text-secondary)' }}>
|
|
37
36
|
{searchedAt}
|
|
38
37
|
</td>
|
|
39
|
-
<td className={
|
|
40
|
-
<span className={
|
|
38
|
+
<td className={'px-4 py-4 whitespace-nowrap text-sm'} style={{ color: 'var(--text-secondary)' }}>
|
|
39
|
+
<span className={'px-2 inline-flex text-xs leading-5 font-semibold rounded-full'} style={{ backgroundColor: 'var(--icon-button-secondary)', color: 'var(--text-main)' }}>
|
|
41
40
|
Not Found
|
|
42
41
|
</span>
|
|
43
42
|
</td>
|
|
44
|
-
<td className={
|
|
45
|
-
No exact match for <strong style={
|
|
43
|
+
<td className={'px-4 py-4 text-sm whitespace-normal'} style={{ color: 'var(--text-secondary)' }}>
|
|
44
|
+
No exact match for <strong style={{ color: 'var(--text-main)' }}>{developerName}</strong> (based on name and email) was found on this list.
|
|
46
45
|
</td>
|
|
47
46
|
</tr>
|
|
48
47
|
);
|
|
49
48
|
|
|
50
|
-
const DomainRow = ({ source, searchedAt, developerName
|
|
51
|
-
<tr className={
|
|
52
|
-
<td className={
|
|
53
|
-
<td className={
|
|
54
|
-
<td className={
|
|
55
|
-
<td className={
|
|
56
|
-
<td className={
|
|
57
|
-
<span className={
|
|
49
|
+
const DomainRow = ({ source, searchedAt, developerName }: { source: DomainSource, searchedAt: string, developerName: string }) => (
|
|
50
|
+
<tr className={'transition-colors hover:bg-black/5'}>
|
|
51
|
+
<td className={'px-4 py-4 whitespace-normal text-sm'} style={{ color: 'var(--text-secondary)' }}>{source.country || 'N/A'}</td>
|
|
52
|
+
<td className={'px-4 py-4 whitespace-normal text-sm'} style={{ color: 'var(--text-secondary)' }}>{source.entityType || 'N/A'}</td>
|
|
53
|
+
<td className={'px-4 py-4 whitespace-normal text-sm font-medium'} style={{ color: 'var(--text-main)' }}>{source.entityName || source.url}</td>
|
|
54
|
+
<td className={'px-4 py-4 whitespace-nowrap text-sm'} style={{ color: 'var(--text-secondary)' }}>{searchedAt}</td>
|
|
55
|
+
<td className={'px-4 py-4 whitespace-nowrap text-sm'} style={{ color: 'var(--text-secondary)' }}>
|
|
56
|
+
<span className={'px-2 inline-flex text-xs leading-5 font-semibold rounded-full'} style={{ backgroundColor: 'var(--icon-button-secondary)', color: 'var(--text-main)' }}>
|
|
58
57
|
Not Found
|
|
59
58
|
</span>
|
|
60
59
|
</td>
|
|
61
|
-
<td className={
|
|
62
|
-
No profile matching <strong style={
|
|
60
|
+
<td className={'px-4 py-4 text-sm whitespace-normal'} style={{ color: 'var(--text-secondary)' }}>
|
|
61
|
+
No profile matching <strong style={{ color: 'var(--text-main)' }}>{developerName}</strong> (based on name and email) was found at this domain.
|
|
63
62
|
</td>
|
|
64
63
|
</tr>
|
|
65
64
|
);
|
|
66
65
|
|
|
67
66
|
|
|
68
|
-
const AppendixTables: React.FC<AppendixTableProps> = ({ type, sources, searchedAt, developerName
|
|
67
|
+
const AppendixTables: React.FC<AppendixTableProps> = ({ type, sources, searchedAt, developerName }) => {
|
|
69
68
|
const [visibleCount, setVisibleCount] = useState(PAGE_SIZE);
|
|
70
69
|
|
|
71
|
-
const isRecruiter = view === 'recruiter';
|
|
72
|
-
|
|
73
70
|
const formattedDate = new Date(searchedAt).toLocaleString(undefined, {
|
|
74
71
|
year: 'numeric', month: 'short', day: 'numeric',
|
|
75
72
|
hour: 'numeric', minute: '2-digit',
|
|
@@ -103,36 +100,36 @@ const AppendixTables: React.FC<AppendixTableProps> = ({ type, sources, searchedA
|
|
|
103
100
|
|
|
104
101
|
return (
|
|
105
102
|
<div>
|
|
106
|
-
<div className={
|
|
107
|
-
<table className={
|
|
108
|
-
<thead className={
|
|
103
|
+
<div className={'overflow-x-auto rounded-lg border'} style={{ borderColor: 'var(--icon-button-secondary)', backgroundColor: 'var(--content-card-background)' }}>
|
|
104
|
+
<table className={'min-w-full'}>
|
|
105
|
+
<thead className={''}>
|
|
109
106
|
<tr>
|
|
110
107
|
{headers.map(header => (
|
|
111
|
-
<th key={header} scope="col" className={
|
|
108
|
+
<th key={header} scope="col" className={'px-4 py-3 text-left text-xs font-semibold uppercase tracking-wider'} style={{ color: 'var(--text-secondary)' }}>
|
|
112
109
|
{header}
|
|
113
110
|
</th>
|
|
114
111
|
))}
|
|
115
112
|
</tr>
|
|
116
113
|
</thead>
|
|
117
|
-
<tbody className={
|
|
114
|
+
<tbody className={''}>
|
|
118
115
|
{visibleParsedSources.map((source, index) =>
|
|
119
116
|
type === 'sanctions'
|
|
120
|
-
? <SanctionsRow key={index} source={source as SanctionSource} searchedAt={formattedDate} developerName={developerName}
|
|
121
|
-
: <DomainRow key={index} source={source as DomainSource} searchedAt={formattedDate} developerName={developerName}
|
|
117
|
+
? <SanctionsRow key={index} source={source as SanctionSource} searchedAt={formattedDate} developerName={developerName} />
|
|
118
|
+
: <DomainRow key={index} source={source as DomainSource} searchedAt={formattedDate} developerName={developerName} />
|
|
122
119
|
)}
|
|
123
120
|
</tbody>
|
|
124
121
|
</table>
|
|
125
122
|
</div>
|
|
126
123
|
{parsedSources.length > PAGE_SIZE && (
|
|
127
|
-
<div className={
|
|
124
|
+
<div className={'mt-4 flex items-center justify-between text-sm'} style={{ color: 'var(--text-secondary)' }}>
|
|
128
125
|
<p>
|
|
129
126
|
Showing {Math.min(visibleCount, parsedSources.length)} of {parsedSources.length} entries
|
|
130
127
|
</p>
|
|
131
128
|
{visibleCount < parsedSources.length && (
|
|
132
129
|
<button
|
|
133
130
|
onClick={handleLoadMore}
|
|
134
|
-
className={
|
|
135
|
-
style={
|
|
131
|
+
className={'font-medium underline-offset-2 hover:underline'}
|
|
132
|
+
style={{ color: 'var(--icon-accent)' }}
|
|
136
133
|
>
|
|
137
134
|
Load More
|
|
138
135
|
</button>
|
|
@@ -8,25 +8,25 @@ interface IpRiskAnalysisDisplayProps {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
const FindingRow = ({ finding }: { finding: IpRiskAnalysisFinding }) => (
|
|
11
|
-
<div className="grid grid-cols-1 md:grid-cols-3 gap-x-8 gap-y-2 py-4 border-b
|
|
11
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-x-8 gap-y-2 py-4 border-b" style={{ borderColor: 'var(--icon-button-secondary)' }}>
|
|
12
12
|
<div className="md:col-span-1">
|
|
13
|
-
<p className="font-semibold
|
|
13
|
+
<p className="font-semibold" style={{ color: 'var(--text-main)' }}>{finding.label}</p>
|
|
14
14
|
</div>
|
|
15
15
|
<div className="md:col-span-2">
|
|
16
|
-
<p
|
|
17
|
-
<p className="text-xs
|
|
16
|
+
<p style={{ color: 'var(--text-secondary)' }}>{finding.details}</p>
|
|
17
|
+
<p className="text-xs mt-1 italic" style={{ color: 'var(--text-secondary)' }}>{finding.implication}</p>
|
|
18
18
|
</div>
|
|
19
19
|
</div>
|
|
20
20
|
);
|
|
21
21
|
|
|
22
22
|
const Section = ({ section }: { section: IpRiskSection }) => (
|
|
23
23
|
<div className="py-3">
|
|
24
|
-
<h4 className="text-lg font-semibold mb-2
|
|
24
|
+
<h4 className="text-lg font-semibold mb-2" style={{ color: 'var(--text-main)' }}>{section.title}</h4>
|
|
25
25
|
<div className="space-y-3">
|
|
26
26
|
{section.items.map((item, idx) => (
|
|
27
|
-
<div key={idx}
|
|
28
|
-
<p className="font-medium
|
|
29
|
-
<p className="text-sm
|
|
27
|
+
<div key={idx}>
|
|
28
|
+
<p className="font-medium" style={{ color: 'var(--text-main)' }}>{item.label}:</p>
|
|
29
|
+
<p className="text-sm" style={{ color: 'var(--text-secondary)' }}>{item.text}</p>
|
|
30
30
|
</div>
|
|
31
31
|
))}
|
|
32
32
|
</div>
|
|
@@ -36,9 +36,9 @@ const Section = ({ section }: { section: IpRiskSection }) => (
|
|
|
36
36
|
const IpRiskAnalysisDisplay = ({ ipRiskAnalysis }: IpRiskAnalysisDisplayProps) => {
|
|
37
37
|
if (!ipRiskAnalysis || !ipRiskAnalysis.checked || !ipRiskAnalysis.findings || ipRiskAnalysis.findings.length === 0) {
|
|
38
38
|
return (
|
|
39
|
-
<div className="mt-6 p-4
|
|
40
|
-
<FiInfo className="h-5 w-5
|
|
41
|
-
<p className="text-sm
|
|
39
|
+
<div className="mt-6 p-4 rounded-lg flex items-center" style={{ backgroundColor: 'var(--content-card-background)' }}>
|
|
40
|
+
<FiInfo className="h-5 w-5 mr-3 flex-shrink-0" style={{ color: 'var(--text-secondary)' }} />
|
|
41
|
+
<p className="text-sm" style={{ color: 'var(--text-secondary)' }}>IP risk analysis was not performed for this assessment.</p>
|
|
42
42
|
</div>
|
|
43
43
|
);
|
|
44
44
|
}
|
|
@@ -47,7 +47,7 @@ const IpRiskAnalysisDisplay = ({ ipRiskAnalysis }: IpRiskAnalysisDisplayProps) =
|
|
|
47
47
|
|
|
48
48
|
if (sections) {
|
|
49
49
|
return (
|
|
50
|
-
<div className="mt-
|
|
50
|
+
<div className="mt-2 divide-y" style={{ borderColor: 'var(--icon-button-secondary)' }}>
|
|
51
51
|
<Section section={sections.general} />
|
|
52
52
|
<Section section={sections.location} />
|
|
53
53
|
<Section section={sections.reputational} />
|
|
@@ -57,7 +57,7 @@ const IpRiskAnalysisDisplay = ({ ipRiskAnalysis }: IpRiskAnalysisDisplayProps) =
|
|
|
57
57
|
|
|
58
58
|
return (
|
|
59
59
|
<div className="mt-6">
|
|
60
|
-
<div className="divide-y
|
|
60
|
+
<div className="divide-y" style={{ borderColor: 'var(--icon-button-secondary)' }}>
|
|
61
61
|
{findings.map((finding, index) => (
|
|
62
62
|
<FindingRow key={index} finding={finding} />
|
|
63
63
|
))}
|
|
@@ -13,8 +13,6 @@ interface Platform {
|
|
|
13
13
|
interface ProviderInsightsProps {
|
|
14
14
|
platforms: Platform[];
|
|
15
15
|
insights: { [provider: string]: ProviderInsight };
|
|
16
|
-
variant?: 'public' | 'private';
|
|
17
|
-
view?: 'recruiter' | 'individual';
|
|
18
16
|
}
|
|
19
17
|
|
|
20
18
|
const providerIcons: { [key: string]: IconType } = {
|
|
@@ -29,14 +27,11 @@ const providerIcons: { [key: string]: IconType } = {
|
|
|
29
27
|
'Google Scholar': FaGoogle,
|
|
30
28
|
};
|
|
31
29
|
|
|
32
|
-
export default function ProviderInsights({ platforms, insights
|
|
30
|
+
export default function ProviderInsights({ platforms, insights }: ProviderInsightsProps) {
|
|
33
31
|
if (!platforms || platforms.length === 0) {
|
|
34
32
|
return null;
|
|
35
33
|
}
|
|
36
34
|
|
|
37
|
-
const isPublic = variant === 'public';
|
|
38
|
-
const isRecruiter = view === 'recruiter';
|
|
39
|
-
|
|
40
35
|
return (
|
|
41
36
|
<div className="space-y-6">
|
|
42
37
|
{platforms.map(platform => {
|
|
@@ -51,22 +46,22 @@ export default function ProviderInsights({ platforms, insights, variant = 'priva
|
|
|
51
46
|
return (
|
|
52
47
|
<div
|
|
53
48
|
key={platform.name}
|
|
54
|
-
className={
|
|
55
|
-
style={
|
|
49
|
+
className={'rounded-xl shadow-sm border'}
|
|
50
|
+
style={{ backgroundColor: 'var(--content-card-background)', borderColor: 'var(--icon-button-secondary)' }}
|
|
56
51
|
>
|
|
57
52
|
{/* Header */}
|
|
58
53
|
<div
|
|
59
54
|
className="p-4 border-b"
|
|
60
|
-
style={
|
|
55
|
+
style={{ borderColor: 'var(--icon-button-secondary)' }}
|
|
61
56
|
>
|
|
62
57
|
<div className="flex items-center justify-between">
|
|
63
58
|
<div className="flex items-center space-x-3">
|
|
64
|
-
{Icon && <Icon className={
|
|
65
|
-
<h3 className={
|
|
59
|
+
{Icon && <Icon className={'h-6 w-6'} style={{ color: 'var(--text-secondary)' }} />}
|
|
60
|
+
<h3 className={'font-semibold text-lg'} style={{ color: 'var(--text-main)' }}>{platform.name}</h3>
|
|
66
61
|
</div>
|
|
67
62
|
{observedDate && (
|
|
68
|
-
<span className={
|
|
69
|
-
|
|
63
|
+
<span className={'text-sm'} style={{ color: 'var(--text-secondary)' }}>
|
|
64
|
+
Observed: {observedDate}
|
|
70
65
|
</span>
|
|
71
66
|
)}
|
|
72
67
|
</div>
|
|
@@ -75,8 +70,8 @@ export default function ProviderInsights({ platforms, insights, variant = 'priva
|
|
|
75
70
|
href={platform.url}
|
|
76
71
|
target="_blank"
|
|
77
72
|
rel="noopener noreferrer"
|
|
78
|
-
className={
|
|
79
|
-
style={
|
|
73
|
+
className={'mt-2 inline-block text-sm font-medium underline-offset-2 hover:underline'}
|
|
74
|
+
style={{ color: 'var(--icon-accent)' }}
|
|
80
75
|
>
|
|
81
76
|
{platform.handle || 'View Profile'}
|
|
82
77
|
</a>
|
|
@@ -87,23 +82,23 @@ export default function ProviderInsights({ platforms, insights, variant = 'priva
|
|
|
87
82
|
{providerInsight && (
|
|
88
83
|
<div className="p-4">
|
|
89
84
|
{/* Summary */}
|
|
90
|
-
<p className={
|
|
85
|
+
<p className={'text-sm mb-4'} style={{ color: 'var(--text-secondary)' }}>
|
|
91
86
|
{providerInsight.summary}
|
|
92
87
|
</p>
|
|
93
88
|
|
|
94
89
|
{/* Data Points */}
|
|
95
90
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
96
91
|
{providerInsight.data_points.map((point, index) => (
|
|
97
|
-
<div key={index} className={
|
|
92
|
+
<div key={index} className={'rounded-lg p-4'} style={{ backgroundColor: 'var(--content-card-background)/90' }}>
|
|
98
93
|
<div className="flex justify-between items-baseline mb-2">
|
|
99
|
-
<h4 className={
|
|
94
|
+
<h4 className={'font-medium'} style={{ color: 'var(--text-main)' }}>
|
|
100
95
|
{point.label}
|
|
101
96
|
</h4>
|
|
102
|
-
<span className={
|
|
97
|
+
<span className={'font-bold'} style={{ color: 'var(--text-main)' }}>
|
|
103
98
|
{point.value}
|
|
104
99
|
</span>
|
|
105
100
|
</div>
|
|
106
|
-
<p className={
|
|
101
|
+
<p className={'text-sm'} style={{ color: 'var(--text-secondary)' }}>
|
|
107
102
|
{point.significance}
|
|
108
103
|
</p>
|
|
109
104
|
</div>
|
|
@@ -15,13 +15,11 @@ interface ReportHeaderProps {
|
|
|
15
15
|
score: number | undefined;
|
|
16
16
|
isPublic: boolean;
|
|
17
17
|
badgeImageUrl: string;
|
|
18
|
-
type?: 'recruiter' | 'individual';
|
|
19
18
|
}
|
|
20
19
|
|
|
21
|
-
const ReportHeader = ({ badgeId, developerName, updatedAt, score = 0, isPublic, badgeImageUrl
|
|
20
|
+
const ReportHeader = ({ badgeId, developerName, updatedAt, score = 0, isPublic, badgeImageUrl }: ReportHeaderProps) => {
|
|
22
21
|
// Use the dynamic image if available, otherwise fall back to the score-based one.
|
|
23
22
|
const finalBadgeImageUrl = badgeImageUrl || getBadgeImageUrl(score);
|
|
24
|
-
const isRecruiter = type === 'recruiter';
|
|
25
23
|
|
|
26
24
|
const formattedDate = updatedAt ? new Date(updatedAt).toLocaleString(undefined, {
|
|
27
25
|
year: 'numeric',
|
|
@@ -31,38 +29,34 @@ const ReportHeader = ({ badgeId, developerName, updatedAt, score = 0, isPublic,
|
|
|
31
29
|
|
|
32
30
|
return (
|
|
33
31
|
<div
|
|
34
|
-
className={
|
|
35
|
-
|
|
36
|
-
? 'mb-8 p-6 rounded-xl shadow-lg flex flex-col md:flex-row items-start md:items-center justify-between gap-6 border'
|
|
37
|
-
: 'mb-8 p-6 bg-white/80 dark:bg-neutral-900/80 backdrop-blur-sm rounded-lg shadow-lg flex flex-col md:flex-row items-start md:items-center justify-between gap-6'
|
|
38
|
-
}
|
|
39
|
-
style={isRecruiter ? { backgroundColor: 'var(--content-card-background)', borderColor: 'var(--icon-button-secondary)' } : undefined}
|
|
32
|
+
className={'mb-8 p-6 rounded-xl shadow-lg flex flex-col md:flex-row items-start md:items-center justify-between gap-6 border'}
|
|
33
|
+
style={{ backgroundColor: 'var(--content-card-background)', borderColor: 'var(--icon-button-secondary)' }}
|
|
40
34
|
>
|
|
41
35
|
{/* Left Section */}
|
|
42
36
|
<div className="flex items-center text-left md:text-center gap-5">
|
|
43
37
|
<Image src={finalBadgeImageUrl} alt="KYD Badge" width={100} height={100} unoptimized />
|
|
44
38
|
<div className='flex flex-col'>
|
|
45
|
-
<h1 className={
|
|
39
|
+
<h1 className={'font-bold text-lg'} style={{ color: 'var(--text-main)' }}>
|
|
46
40
|
KYD Self-Check™
|
|
47
41
|
</h1>
|
|
48
|
-
<p className={
|
|
49
|
-
|
|
42
|
+
<p className={'text-sm'} style={{ color: 'var(--text-secondary)' }}>
|
|
43
|
+
Private Report
|
|
50
44
|
</p>
|
|
51
45
|
</div>
|
|
52
46
|
</div>
|
|
53
47
|
|
|
54
48
|
{/* Middle Section */}
|
|
55
49
|
<div className="text-left md:text-center">
|
|
56
|
-
<p className={
|
|
57
|
-
<p className={
|
|
50
|
+
<p className={'text-sm'} style={{ color: 'var(--text-secondary)' }}>Developer</p>
|
|
51
|
+
<p className={'font-semibold text-2xl'} style={{ color: 'var(--text-main)' }}>{developerName || 'N/A'}</p>
|
|
58
52
|
</div>
|
|
59
53
|
|
|
60
54
|
{/* Right Section */}
|
|
61
|
-
<div className={
|
|
62
|
-
<p><span className={
|
|
63
|
-
<p><span className={
|
|
64
|
-
<p><span className={
|
|
65
|
-
<p><span className={
|
|
55
|
+
<div className={'text-left text-sm space-y-1'} style={{ color: 'var(--text-secondary)' }}>
|
|
56
|
+
<p><span className={'font-semibold'} style={{ color: 'var(--text-main)' }}>Requested By:</span> {developerName || 'N/A'}</p>
|
|
57
|
+
<p><span className={'font-semibold'} style={{ color: 'var(--text-main)' }}>Organization:</span> Unaffiliated</p>
|
|
58
|
+
<p><span className={'font-semibold'} style={{ color: 'var(--text-main)' }}>Date Generated:</span> {formattedDate}</p>
|
|
59
|
+
<p><span className={'font-semibold'} style={{ color: 'var(--text-main)' }}>Report ID:</span> {badgeId}</p>
|
|
66
60
|
</div>
|
|
67
61
|
</div>
|
|
68
62
|
);
|
|
@@ -127,7 +127,7 @@ const ShareButton = ({ badgeId, shareTitle, shareText, buttonText, className, is
|
|
|
127
127
|
<button
|
|
128
128
|
onClick={handleButtonClick}
|
|
129
129
|
disabled={!badgeId || !url || isToggling}
|
|
130
|
-
className={className || "flex items-center px-4 py-2
|
|
130
|
+
className={className || "flex items-center px-4 py-2 text-white rounded-md transition-colors disabled:opacity-50 disabled:cursor-not-allowed bg-[var(--icon-accent)] hover:bg-[var(--icon-accent-hover)]"}
|
|
131
131
|
>
|
|
132
132
|
{isToggling ? (
|
|
133
133
|
<FiRefreshCw className="animate-spin" />
|
|
@@ -138,7 +138,7 @@ const ShareButton = ({ badgeId, shareTitle, shareText, buttonText, className, is
|
|
|
138
138
|
</button>
|
|
139
139
|
|
|
140
140
|
{isMenuOpen && (
|
|
141
|
-
<div className="absolute right-0 mt-2 w-56 origin-top-right
|
|
141
|
+
<div className="absolute right-0 mt-2 w-56 origin-top-right rounded-md shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none z-20" style={{ backgroundColor: 'var(--content-card-background)' }}>
|
|
142
142
|
<div className="py-1">
|
|
143
143
|
{socialPlatforms.map((platform) => {
|
|
144
144
|
const Icon = platform.icon;
|
|
@@ -149,27 +149,30 @@ const ShareButton = ({ badgeId, shareTitle, shareText, buttonText, className, is
|
|
|
149
149
|
target="_blank"
|
|
150
150
|
rel="noopener noreferrer"
|
|
151
151
|
onClick={() => setIsMenuOpen(false)}
|
|
152
|
-
className="flex items-center w-full px-4 py-2 text-sm
|
|
152
|
+
className="flex items-center w-full px-4 py-2 text-sm hover:bg-black/5"
|
|
153
|
+
style={{ color: 'var(--text-main)' }}
|
|
153
154
|
>
|
|
154
155
|
<Icon className={`mr-3 h-5 w-5 ${platform.color}`} />
|
|
155
156
|
<span>Share on {platform.name}</span>
|
|
156
157
|
</a>
|
|
157
158
|
);
|
|
158
159
|
})}
|
|
159
|
-
<div className="
|
|
160
|
+
<div className="my-1" style={{ borderTop: '1px solid var(--icon-button-secondary)' }}></div>
|
|
160
161
|
<button
|
|
161
162
|
onClick={handleCopyLink}
|
|
162
|
-
className="flex items-center w-full px-4 py-2 text-sm
|
|
163
|
+
className="flex items-center w-full px-4 py-2 text-sm hover:bg-black/5"
|
|
164
|
+
style={{ color: 'var(--text-main)' }}
|
|
163
165
|
>
|
|
164
166
|
<FiLink className="mr-3 h-5 w-5" />
|
|
165
167
|
<span>Copy Link</span>
|
|
166
168
|
</button>
|
|
167
169
|
{isNativeShareSupported && (
|
|
168
170
|
<>
|
|
169
|
-
<div className="
|
|
171
|
+
<div className="my-1" style={{ borderTop: '1px solid var(--icon-button-secondary)' }}></div>
|
|
170
172
|
<button
|
|
171
173
|
onClick={handleNativeShare}
|
|
172
|
-
className="flex items-center w-full px-4 py-2 text-sm
|
|
174
|
+
className="flex items-center w-full px-4 py-2 text-sm hover:bg-black/5"
|
|
175
|
+
style={{ color: 'var(--text-main)' }}
|
|
173
176
|
>
|
|
174
177
|
<FiShare2 className="mr-3 h-5 w-5" />
|
|
175
178
|
<span>Share via...</span>
|