kyd-shared-badge 0.2.15 → 0.2.17
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 +58 -10
- package/src/components/AppendixTables.tsx +83 -44
- package/src/types.ts +3 -0
package/package.json
CHANGED
|
@@ -207,6 +207,64 @@ const SharedBadgeDisplay = ({ badgeData }: { badgeData: PublicBadgeData }) => {
|
|
|
207
207
|
<div className={'prose prose-sm max-w-none space-y-4 mb-6'} style={{ color: 'var(--text-secondary)' }}>
|
|
208
208
|
<p>{riskScore?.description || ''}</p>
|
|
209
209
|
</div>
|
|
210
|
+
{(() => {
|
|
211
|
+
const ss = assessmentResult?.screening_sources;
|
|
212
|
+
const ofacMatches = ss?.ofac_screen?.matches && (ss.ofac_screen.matches.length > 0);
|
|
213
|
+
const cslDetails = ss?.csl_details && (Array.isArray(ss.csl_details) ? ss.csl_details.length > 0 : true);
|
|
214
|
+
const fbiMatches = ss?.fbi_matches && (ss.fbi_matches.length > 0);
|
|
215
|
+
if (!(ofacMatches || cslDetails || fbiMatches)) return null;
|
|
216
|
+
return (
|
|
217
|
+
<div className={'mb-8 rounded-lg border p-4'} style={{ borderColor: 'var(--icon-button-secondary)', backgroundColor: 'var(--content-card-background)' }}>
|
|
218
|
+
<h4 className={'text-lg font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>3A. Sanctions Matches</h4>
|
|
219
|
+
{/* OFAC matches */}
|
|
220
|
+
{ofacMatches && (
|
|
221
|
+
<div className={'mb-4'}>
|
|
222
|
+
<h5 className={'font-semibold mb-2'} style={{ color: 'var(--text-main)' }}>OFAC API Matches</h5>
|
|
223
|
+
<ul className={'list-disc pl-5 text-sm'} style={{ color: 'var(--text-secondary)' }}>
|
|
224
|
+
{ss!.ofac_screen!.matches!.map((m: any, i: number) => {
|
|
225
|
+
const s = m?.sanction || {};
|
|
226
|
+
return (
|
|
227
|
+
<li key={i} className={'mb-1'}>
|
|
228
|
+
<span className={'font-medium'} style={{ color: 'var(--text-main)' }}>{s.name || 'Unknown'}</span>
|
|
229
|
+
{s.programs && s.programs.length ? <> — Programs: {s.programs.join(', ')}</> : null}
|
|
230
|
+
{s.entityLink ? <> — <a href={s.entityLink} target='_blank' rel='noopener noreferrer' className={'underline'} style={{ color: 'var(--icon-accent)' }}>Details</a></> : null}
|
|
231
|
+
</li>
|
|
232
|
+
);
|
|
233
|
+
})}
|
|
234
|
+
</ul>
|
|
235
|
+
<details className={'mt-2'}>
|
|
236
|
+
<summary className={'cursor-pointer text-sm font-semibold'} style={{ color: 'var(--text-main)' }}>View OFAC Raw JSON</summary>
|
|
237
|
+
<pre className={'mt-2 overflow-auto text-xs p-3 rounded'} style={{ background: 'rgba(0,0,0,0.04)', color: 'var(--text-main)' }}>{JSON.stringify(ss!.ofac_screen!.raw, null, 2)}</pre>
|
|
238
|
+
</details>
|
|
239
|
+
</div>
|
|
240
|
+
)}
|
|
241
|
+
{/* CSL details */}
|
|
242
|
+
{cslDetails && (
|
|
243
|
+
<div className={'mb-4'}>
|
|
244
|
+
<h5 className={'font-semibold mb-2'} style={{ color: 'var(--text-main)' }}>U.S. CSL Details</h5>
|
|
245
|
+
<details>
|
|
246
|
+
<summary className={'cursor-pointer text-sm font-semibold'} style={{ color: 'var(--text-main)' }}>View CSL Raw JSON</summary>
|
|
247
|
+
<pre className={'mt-2 overflow-auto text-xs p-3 rounded'} style={{ background: 'rgba(0,0,0,0.04)', color: 'var(--text-main)' }}>{JSON.stringify(ss!.csl_details, null, 2)}</pre>
|
|
248
|
+
</details>
|
|
249
|
+
</div>
|
|
250
|
+
)}
|
|
251
|
+
{/* FBI matches */}
|
|
252
|
+
{fbiMatches && (
|
|
253
|
+
<div className={'mb-2'}>
|
|
254
|
+
<h5 className={'font-semibold mb-2'} style={{ color: 'var(--text-main)' }}>FBI Wanted List Matches</h5>
|
|
255
|
+
<ul className={'list-disc pl-5 text-sm'} style={{ color: 'var(--text-secondary)' }}>
|
|
256
|
+
{ss!.fbi_matches!.map((f: any, i: number) => (
|
|
257
|
+
<li key={i}>
|
|
258
|
+
<span className={'font-medium'} style={{ color: 'var(--text-main)' }}>{f.title || 'Match'}</span>
|
|
259
|
+
{f.url ? <> — <a href={f.url} target='_blank' rel='noopener noreferrer' className={'underline'} style={{ color: 'var(--icon-accent)' }}>Details</a></> : null}
|
|
260
|
+
</li>
|
|
261
|
+
))}
|
|
262
|
+
</ul>
|
|
263
|
+
</div>
|
|
264
|
+
)}
|
|
265
|
+
</div>
|
|
266
|
+
);
|
|
267
|
+
})()}
|
|
210
268
|
<IpRiskAnalysisDisplay ipRiskAnalysis={screening_sources?.ip_risk_analysis} />
|
|
211
269
|
</>
|
|
212
270
|
)}
|
|
@@ -259,16 +317,6 @@ const SharedBadgeDisplay = ({ badgeData }: { badgeData: PublicBadgeData }) => {
|
|
|
259
317
|
/>
|
|
260
318
|
);
|
|
261
319
|
})()}
|
|
262
|
-
{screening_sources.sanctions_sublists && screening_sources.sanctions_sublists.length > 0 && (
|
|
263
|
-
<details className="mt-3">
|
|
264
|
-
<summary className={'cursor-pointer text-sm font-semibold'} style={{ color: 'var(--text-main)' }}>View all screened sub-lists</summary>
|
|
265
|
-
<ul className="mt-2 list-disc pl-5 text-sm" style={{ color: 'var(--text-secondary)' }}>
|
|
266
|
-
{screening_sources.sanctions_sublists.map((name, idx) => (
|
|
267
|
-
<li key={idx}>{name}</li>
|
|
268
|
-
))}
|
|
269
|
-
</ul>
|
|
270
|
-
</details>
|
|
271
|
-
)}
|
|
272
320
|
</div>
|
|
273
321
|
<div>
|
|
274
322
|
<h4 className={'text-xl font-bold mb-4'} style={{ color: 'var(--text-main)' }}>Country-specific Entity Affiliations</h4>
|
|
@@ -7,6 +7,7 @@ interface SanctionSource {
|
|
|
7
7
|
issuingEntity: string;
|
|
8
8
|
listName: string;
|
|
9
9
|
matched?: boolean;
|
|
10
|
+
sublists?: string[];
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
interface DomainSource {
|
|
@@ -25,48 +26,73 @@ interface AppendixTableProps {
|
|
|
25
26
|
developerName: string;
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
const SanctionsRow = ({
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
29
|
+
const SanctionsRow = ({
|
|
30
|
+
source,
|
|
31
|
+
searchedAt,
|
|
32
|
+
developerName,
|
|
33
|
+
onToggle,
|
|
34
|
+
expanded,
|
|
35
|
+
colSpan,
|
|
36
|
+
}: {
|
|
37
|
+
source: SanctionSource;
|
|
38
|
+
searchedAt: string;
|
|
39
|
+
developerName: string;
|
|
40
|
+
onToggle: () => void;
|
|
41
|
+
expanded: boolean;
|
|
42
|
+
colSpan: number;
|
|
43
|
+
}) => (
|
|
44
|
+
<>
|
|
45
|
+
<tr className={'transition-colors'} style={source.matched ? { backgroundColor: 'rgba(236,102,98,0.08)' } : undefined}>
|
|
46
|
+
<td className={'px-4 py-4 whitespace-nowrap align-top text-sm font-medium'} style={{ color: 'var(--text-main)' }}>
|
|
47
|
+
<div className="flex flex-col gap-1">
|
|
48
|
+
<span>{source.issuingEntity}</span>
|
|
37
49
|
{source.sublists && source.sublists.length > 0 && (
|
|
38
|
-
<
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
50
|
+
<button
|
|
51
|
+
type="button"
|
|
52
|
+
onClick={onToggle}
|
|
53
|
+
className={'text-xs inline-flex items-center gap-1 text-left'}
|
|
54
|
+
style={{ color: 'var(--text-secondary)' }}
|
|
55
|
+
aria-expanded={expanded}
|
|
56
|
+
>
|
|
57
|
+
<span style={{ display: 'inline-block', transform: expanded ? 'rotate(90deg)' : 'rotate(0deg)', transition: 'transform 150ms ease' }} className={'mr-1'}>▶</span>
|
|
58
|
+
<span>View sub-lists</span>
|
|
59
|
+
</button>
|
|
46
60
|
)}
|
|
47
61
|
</div>
|
|
48
|
-
</
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
)}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
62
|
+
</td>
|
|
63
|
+
<td className={'px-4 py-4 whitespace-normal align-top text-sm'} style={{ color: 'var(--text-secondary)' }}>
|
|
64
|
+
{source.listName}
|
|
65
|
+
</td>
|
|
66
|
+
<td className={'px-4 py-4 whitespace-nowrap align-top text-sm'} style={{ color: 'var(--text-secondary)' }}>
|
|
67
|
+
{searchedAt}
|
|
68
|
+
</td>
|
|
69
|
+
<td className={'px-4 py-4 whitespace-nowrap align-top text-sm'} style={{ color: 'var(--text-secondary)' }}>
|
|
70
|
+
{source.matched ? (
|
|
71
|
+
<span className={'text-xs font-semibold'} style={{ color: 'var(--text-main)' }}>Match</span>
|
|
72
|
+
) : (
|
|
73
|
+
<span className={'text-xs'} style={{ color: 'var(--text-secondary)' }}>Not Found</span>
|
|
74
|
+
)}
|
|
75
|
+
</td>
|
|
76
|
+
<td className={'px-4 py-4 text-sm whitespace-normal align-top'} style={{ color: 'var(--text-secondary)' }}>
|
|
77
|
+
{source.matched
|
|
78
|
+
? (<span>Exact or strong match for <strong style={{ color: 'var(--text-main)' }}>{developerName}</strong> was found on this list.</span>)
|
|
79
|
+
: (<span>No exact match for <strong style={{ color: 'var(--text-main)' }}>{developerName}</strong> was found on this list.</span>)}
|
|
80
|
+
</td>
|
|
81
|
+
</tr>
|
|
82
|
+
{expanded && source.sublists && source.sublists.length > 0 && (
|
|
83
|
+
<tr>
|
|
84
|
+
<td colSpan={colSpan} className={'px-6 pb-4'}>
|
|
85
|
+
<div className={'mt-1 border-t pt-3'} style={{ borderColor: 'var(--icon-button-secondary)' }}>
|
|
86
|
+
<ul className={'grid grid-cols-1 sm:grid-cols-2 gap-y-1 gap-x-6 text-sm list-disc pl-5'} style={{ color: 'var(--text-secondary)' }}>
|
|
87
|
+
{source.sublists.map((sl, idx) => (
|
|
88
|
+
<li key={idx}>{sl}</li>
|
|
89
|
+
))}
|
|
90
|
+
</ul>
|
|
91
|
+
</div>
|
|
92
|
+
</td>
|
|
93
|
+
</tr>
|
|
94
|
+
)}
|
|
95
|
+
</>
|
|
70
96
|
);
|
|
71
97
|
|
|
72
98
|
const DomainRow = ({ source, searchedAt, developerName }: { source: DomainSource, searchedAt: string, developerName: string }) => (
|
|
@@ -89,6 +115,7 @@ const DomainRow = ({ source, searchedAt, developerName }: { source: DomainSource
|
|
|
89
115
|
|
|
90
116
|
const AppendixTables: React.FC<AppendixTableProps> = ({ type, sources, searchedAt, developerName }) => {
|
|
91
117
|
const [visibleCount, setVisibleCount] = useState(PAGE_SIZE);
|
|
118
|
+
const [expanded, setExpanded] = useState<{ [k: number]: boolean }>({});
|
|
92
119
|
|
|
93
120
|
const formattedDate = new Date(searchedAt).toLocaleString(undefined, {
|
|
94
121
|
year: 'numeric', month: 'short', day: 'numeric',
|
|
@@ -147,11 +174,23 @@ const AppendixTables: React.FC<AppendixTableProps> = ({ type, sources, searchedA
|
|
|
147
174
|
</tr>
|
|
148
175
|
</thead>
|
|
149
176
|
<tbody className={''}>
|
|
150
|
-
{visibleParsedSources.map((source, index) =>
|
|
151
|
-
type === 'sanctions'
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
177
|
+
{visibleParsedSources.map((source, index) => {
|
|
178
|
+
if (type === 'sanctions') {
|
|
179
|
+
const src = source as SanctionSource;
|
|
180
|
+
return (
|
|
181
|
+
<SanctionsRow
|
|
182
|
+
key={index}
|
|
183
|
+
source={src}
|
|
184
|
+
searchedAt={formattedDate}
|
|
185
|
+
developerName={developerName}
|
|
186
|
+
onToggle={() => setExpanded(prev => ({ ...prev, [index]: !prev[index] }))}
|
|
187
|
+
expanded={!!expanded[index]}
|
|
188
|
+
colSpan={headers.length}
|
|
189
|
+
/>
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
return <DomainRow key={index} source={source as DomainSource} searchedAt={formattedDate} developerName={developerName} />
|
|
193
|
+
})}
|
|
155
194
|
</tbody>
|
|
156
195
|
</table>
|
|
157
196
|
</div>
|
package/src/types.ts
CHANGED
|
@@ -160,9 +160,12 @@ export interface AssessmentResult {
|
|
|
160
160
|
checked?: boolean;
|
|
161
161
|
sources?: string[];
|
|
162
162
|
matches?: any[];
|
|
163
|
+
raw?: any;
|
|
163
164
|
};
|
|
164
165
|
sanctions_sources_detailed?: { issuingEntity: string; listName: string; matched?: boolean; sublists?: string[] }[];
|
|
165
166
|
sanctions_sublists?: string[];
|
|
167
|
+
csl_details?: any;
|
|
168
|
+
fbi_matches?: any[];
|
|
166
169
|
};
|
|
167
170
|
optOutScreening?: boolean;
|
|
168
171
|
clientMetadata?: ClientMetadata;
|