kyd-shared-badge 0.3.118 → 0.3.119
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.
|
|
3
|
+
"version": "0.3.119",
|
|
4
4
|
"private": false,
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"module": "./src/index.ts",
|
|
@@ -18,7 +18,9 @@
|
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@ai-sdk/openai": "^2.0.42",
|
|
20
20
|
"@aws-sdk/client-s3": "^3.893.0",
|
|
21
|
+
"@aws-sdk/client-ses": "^3.916.0",
|
|
21
22
|
"@aws-sdk/lib-dynamodb": "^3.893.0",
|
|
23
|
+
"@aws-sdk/s3-request-presigner": "^3.916.0",
|
|
22
24
|
"@chatscope/chat-ui-kit-react": "^2.1.1",
|
|
23
25
|
"@chatscope/chat-ui-kit-styles": "^1.4.0",
|
|
24
26
|
"@knowyourdeveloper/react-bubble-chart": "^1.0.8",
|
|
@@ -34,6 +36,7 @@
|
|
|
34
36
|
"react-hot-toast": "^2.6.0",
|
|
35
37
|
"react-icons": "^5.5.0",
|
|
36
38
|
"recharts": "^2.15.4",
|
|
39
|
+
"stripe": "^19.1.0",
|
|
37
40
|
"tailwind-merge": "^3.3.1",
|
|
38
41
|
"ulid": "^3.0.1"
|
|
39
42
|
},
|
|
@@ -15,6 +15,7 @@ import { FaGithub, FaGitlab, FaStackOverflow, FaLinkedin, FaGoogle, FaKaggle } f
|
|
|
15
15
|
import { SiCredly, SiFiverr } from 'react-icons/si';
|
|
16
16
|
import GaugeCard from './components/GaugeCard';
|
|
17
17
|
import RiskCard from './components/RiskCard';
|
|
18
|
+
import RoleOverviewCard from './components/RoleOverviewCard';
|
|
18
19
|
|
|
19
20
|
import { yellow, green } from './colors';
|
|
20
21
|
import SkillsValidation from './components/SkillsValidation';
|
|
@@ -87,6 +88,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
|
|
|
87
88
|
return undefined;
|
|
88
89
|
}
|
|
89
90
|
})();
|
|
91
|
+
const hasEnterpriseMatch = !!assessmentResult?.enterprise_match;
|
|
90
92
|
|
|
91
93
|
const topBusinessForGenre = (genre: string) => {
|
|
92
94
|
const cats: string[] = (genreMapping)?.[genre] || [];
|
|
@@ -120,28 +122,86 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
|
|
|
120
122
|
}`}
|
|
121
123
|
</style>
|
|
122
124
|
)}
|
|
123
|
-
{/*
|
|
124
|
-
<
|
|
125
|
-
<
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
125
|
+
{/* Overview quadrant layout (mirrors SharedBadgeDisplay overview) */}
|
|
126
|
+
<div className={'mt-6'}>
|
|
127
|
+
<Reveal headless={isHeadless} offsetY={8} durationMs={500}>
|
|
128
|
+
<div className={'grid grid-cols-1 md:grid-cols-2 gap-6'}>
|
|
129
|
+
<div className={`${hasEnterpriseMatch ? '' : 'md:col-span-2'}`}>
|
|
130
|
+
<ReportHeader
|
|
131
|
+
badgeId={badgeId}
|
|
132
|
+
developerName={badgeData.developerName}
|
|
133
|
+
updatedAt={updatedAt}
|
|
134
|
+
score={overallFinalPercent || 0}
|
|
135
|
+
isPublic={true}
|
|
136
|
+
badgeImageUrl={badgeData.badgeImageUrl || ''}
|
|
137
|
+
summary={undefined}
|
|
138
|
+
enterpriseMatch={null}
|
|
139
|
+
countries={(assessmentResult?.screening_sources?.ip_risk_analysis?.raw_data?.countries) || []}
|
|
140
|
+
accountAuthenticity={assessmentResult?.account_authenticity}
|
|
141
|
+
companyName={badgeData.companyName}
|
|
142
|
+
sourcesProviders={(badgeData?.connectedAccounts || []).map(a => (a?.name || '').toLowerCase())}
|
|
143
|
+
rightBadgeLayout={!hasEnterpriseMatch}
|
|
144
|
+
/>
|
|
145
|
+
</div>
|
|
146
|
+
{hasEnterpriseMatch && (
|
|
147
|
+
<div>
|
|
148
|
+
{(() => {
|
|
149
|
+
const em = assessmentResult?.enterprise_match;
|
|
150
|
+
if (!em) return null;
|
|
151
|
+
const role = em.role || {};
|
|
152
|
+
return (
|
|
153
|
+
<RoleOverviewCard
|
|
154
|
+
title={'Role Alignment'}
|
|
155
|
+
matchLabel={em.label}
|
|
156
|
+
roleName={role?.name || 'Role'}
|
|
157
|
+
/>
|
|
158
|
+
);
|
|
159
|
+
})()}
|
|
160
|
+
</div>
|
|
161
|
+
)}
|
|
162
|
+
<div>
|
|
163
|
+
{(() => {
|
|
164
|
+
const uiTech = graphInsights?.uiSummary?.technical || {};
|
|
165
|
+
const techPct = Math.round(Number(uiTech?.percent ?? 0));
|
|
166
|
+
const techLabel = uiTech?.label || 'EVIDENCE';
|
|
167
|
+
return (
|
|
168
|
+
<GaugeCard
|
|
169
|
+
title={'KYD Technical'}
|
|
170
|
+
description={'Composite of technical evidence; more right indicates stronger capability'}
|
|
171
|
+
percent={techPct}
|
|
172
|
+
label={techLabel}
|
|
173
|
+
topMovers={[]}
|
|
174
|
+
/>
|
|
175
|
+
);
|
|
176
|
+
})()}
|
|
177
|
+
</div>
|
|
178
|
+
<div>
|
|
179
|
+
{(() => {
|
|
180
|
+
const uiRisk = graphInsights?.uiSummary?.risk || {};
|
|
181
|
+
const riskPctGood = Math.round(Number(uiRisk?.percent_good ?? 0));
|
|
182
|
+
const riskLabel = uiRisk?.label || 'RISK';
|
|
183
|
+
return (
|
|
184
|
+
<RiskCard
|
|
185
|
+
title={'KYD Risk'}
|
|
186
|
+
description={'Lower bar height indicates lower risk exposure'}
|
|
187
|
+
percentGood={riskPctGood}
|
|
188
|
+
label={riskLabel}
|
|
189
|
+
topMovers={[]}
|
|
190
|
+
/>
|
|
191
|
+
);
|
|
192
|
+
})()}
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
</Reveal>
|
|
196
|
+
<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)' }}>
|
|
197
|
+
<Reveal headless={isHeadless}>
|
|
198
|
+
<div className={'kyd-avoid-break'}>
|
|
199
|
+
<h4 className={'text-2xl font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>Skills Footprint</h4>
|
|
200
|
+
<SkillsBubble skillsCategoryRadar={graphInsights?.skillsCategoryRadar} headless={isHeadless} skillsByCategory={assessmentResult?.graph_insights?.skillsByCategory} skillsMeta={assessmentResult?.graph_insights?.skillsMeta} />
|
|
201
|
+
</div>
|
|
202
|
+
</Reveal>
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
145
205
|
{/* Coaching / Evidence under header when present */}
|
|
146
206
|
{(() => {
|
|
147
207
|
const em = assessmentResult.enterprise_match;
|
|
@@ -158,12 +218,6 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
|
|
|
158
218
|
style={{ backgroundColor: 'var(--content-card-background)', borderColor: 'var(--icon-button-secondary)' }}
|
|
159
219
|
>
|
|
160
220
|
<div className={'space-y-12 divide-y'} style={{ borderColor: 'var(--icon-button-secondary)' }}>
|
|
161
|
-
<div className="pt-8 first:pt-0 kyd-avoid-break">
|
|
162
|
-
<Reveal headless={isHeadless} as={'h4'} offsetY={8} durationMs={500} className={'text-2xl font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>Report Summary</Reveal>
|
|
163
|
-
<Reveal headless={isHeadless} as={'div'} offsetY={8} durationMs={500} className={'space-y-12 divide-y'} style={{ borderColor: 'var(--icon-button-secondary)' }}>
|
|
164
|
-
<SummaryCards graphInsights={graphInsights} assessmentResult={assessmentResult} topBusinessForGenre={topBusinessForGenre} />
|
|
165
|
-
</Reveal>
|
|
166
|
-
</div>
|
|
167
221
|
|
|
168
222
|
{/* Enterprise Use Cases (directly under the three summary cards) */}
|
|
169
223
|
{assessmentResult?.enterprise_use_cases?.items && assessmentResult.enterprise_use_cases.items.length > 0 && (
|
|
@@ -5,7 +5,7 @@ import { normalizeLinkedInInput } from './linkedin';
|
|
|
5
5
|
import type { ConnectAccountsProps } from './types';
|
|
6
6
|
import { CheckCircle, Link2, LinkIcon, Unlink, ArrowLeft, ExternalLink, Settings, Shield, InfoIcon } from 'lucide-react';
|
|
7
7
|
import { AnimatePresence, motion } from 'framer-motion';
|
|
8
|
-
import { Button, Input, Spinner, Card, CardHeader, CardContent, CardFooter, CardTitle } from '../ui';
|
|
8
|
+
import { Button, Input, Spinner, Card, CardHeader, CardContent, CardFooter, CardTitle, ProgressCircle } from '../ui';
|
|
9
9
|
import Link from 'next/link';
|
|
10
10
|
import { Tooltip, TooltipTrigger, TooltipProvider, TooltipContent } from '../ui/';
|
|
11
11
|
|
|
@@ -222,8 +222,33 @@ export function ConnectAccounts(props: ConnectAccountsProps) {
|
|
|
222
222
|
return setupAction === 'install' || setupAction === 'created';
|
|
223
223
|
}, [githubConnectedAccount]);
|
|
224
224
|
|
|
225
|
+
// Progress computation
|
|
226
|
+
const targetProviderIds = useMemo(() => {
|
|
227
|
+
const targets = (requiredProviders && requiredProviders.length
|
|
228
|
+
? requiredProviders
|
|
229
|
+
: providers.map(p => p.id))
|
|
230
|
+
.map(id => id.toLowerCase());
|
|
231
|
+
return new Set(targets);
|
|
232
|
+
}, [requiredProviders, providers]);
|
|
233
|
+
const numConnectedTargets = useMemo(() => {
|
|
234
|
+
let count = 0;
|
|
235
|
+
targetProviderIds.forEach(id => { if (connectedIds.has(id)) count += 1; });
|
|
236
|
+
return count;
|
|
237
|
+
}, [targetProviderIds, connectedIds]);
|
|
238
|
+
const progressPercent = useMemo(() => {
|
|
239
|
+
const total = targetProviderIds.size;
|
|
240
|
+
if (!total) return 0;
|
|
241
|
+
return Math.round((numConnectedTargets / total) * 100);
|
|
242
|
+
}, [numConnectedTargets, targetProviderIds]);
|
|
243
|
+
|
|
225
244
|
return (
|
|
226
|
-
|
|
245
|
+
<>
|
|
246
|
+
{providers?.length ? (
|
|
247
|
+
<div className="fixed sm:bottom-6 sm:right-6 bottom-4 right-4 z-50 pointer-events-none">
|
|
248
|
+
<ProgressCircle value={progressPercent} size={68} thickness={6} />
|
|
249
|
+
</div>
|
|
250
|
+
) : null}
|
|
251
|
+
<AnimatePresence initial={false} mode="wait">
|
|
227
252
|
{showDataHandling ? (
|
|
228
253
|
<motion.div
|
|
229
254
|
key="data-handling-card"
|
|
@@ -695,7 +720,8 @@ export function ConnectAccounts(props: ConnectAccountsProps) {
|
|
|
695
720
|
</AnimatePresence>
|
|
696
721
|
</Card>
|
|
697
722
|
)}
|
|
698
|
-
|
|
723
|
+
</AnimatePresence>
|
|
724
|
+
</>
|
|
699
725
|
);
|
|
700
726
|
}
|
|
701
727
|
|
|
@@ -86,7 +86,7 @@ export async function createSupportTicketRoute(req: NextRequest, userId: string)
|
|
|
86
86
|
const inv = await stripe.invoices.list({ customer: stripeCustomerId, status: 'paid', limit: 100, starting_after: startingAfter })
|
|
87
87
|
for (const i of inv.data) totalSpentUsd += (i.total || 0)
|
|
88
88
|
hasMore = inv.has_more
|
|
89
|
-
startingAfter = inv.data.
|
|
89
|
+
startingAfter = inv.data[inv.data.length - 1]?.id
|
|
90
90
|
}
|
|
91
91
|
totalSpentUsd = Math.round(totalSpentUsd) / 100
|
|
92
92
|
}
|
|
@@ -116,7 +116,7 @@ export async function createSupportTicketRoute(req: NextRequest, userId: string)
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
const product = 'KYD Self-check'
|
|
119
|
-
const name = userItem?.fullName || userItem?.email || ''
|
|
119
|
+
const name = userItem?.fullName || userItem?.name || userItem?.email || ''
|
|
120
120
|
const email = userItem?.email || ''
|
|
121
121
|
|
|
122
122
|
const html = `
|
package/src/ui/index.ts
CHANGED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
|
|
4
|
+
export type ProgressCircleProps = {
|
|
5
|
+
value: number
|
|
6
|
+
size?: number
|
|
7
|
+
thickness?: number
|
|
8
|
+
className?: string
|
|
9
|
+
showLabel?: boolean
|
|
10
|
+
label?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function ProgressCircle(props: ProgressCircleProps) {
|
|
14
|
+
const { value, size = 72, thickness = 6, className, showLabel = true, label } = props
|
|
15
|
+
|
|
16
|
+
const clamped = Math.max(0, Math.min(100, Number.isFinite(value) ? value : 0))
|
|
17
|
+
const radius = (size - thickness) / 2
|
|
18
|
+
const circumference = 2 * Math.PI * radius
|
|
19
|
+
const dashOffset = circumference * (1 - clamped / 100)
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<div className={`relative inline-flex items-center justify-center ${className || ''}`} style={{ width: size, height: size }}>
|
|
23
|
+
<svg width={size} height={size} viewBox={`0 0 ${size} ${size}`} className="block">
|
|
24
|
+
<defs>
|
|
25
|
+
<linearGradient id="kyd-progress-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
26
|
+
<stop offset="0%" stopColor="var(--icon-accent)" />
|
|
27
|
+
<stop offset="100%" stopColor="var(--icon-accent-hover)" />
|
|
28
|
+
</linearGradient>
|
|
29
|
+
</defs>
|
|
30
|
+
<circle
|
|
31
|
+
cx={size / 2}
|
|
32
|
+
cy={size / 2}
|
|
33
|
+
r={radius}
|
|
34
|
+
fill="none"
|
|
35
|
+
stroke="var(--icon-button-secondary)"
|
|
36
|
+
strokeOpacity={0.3}
|
|
37
|
+
strokeWidth={thickness}
|
|
38
|
+
/>
|
|
39
|
+
<circle
|
|
40
|
+
cx={size / 2}
|
|
41
|
+
cy={size / 2}
|
|
42
|
+
r={radius}
|
|
43
|
+
fill="none"
|
|
44
|
+
stroke="url(#kyd-progress-gradient)"
|
|
45
|
+
strokeWidth={thickness}
|
|
46
|
+
strokeLinecap="round"
|
|
47
|
+
strokeDasharray={circumference}
|
|
48
|
+
strokeDashoffset={dashOffset}
|
|
49
|
+
style={{ transition: 'stroke-dashoffset 800ms ease' }}
|
|
50
|
+
transform={`rotate(-90 ${size / 2} ${size / 2})`}
|
|
51
|
+
/>
|
|
52
|
+
{/* subtle pulsing halo */}
|
|
53
|
+
<circle
|
|
54
|
+
cx={size / 2}
|
|
55
|
+
cy={size / 2}
|
|
56
|
+
r={radius + thickness / 2}
|
|
57
|
+
fill="none"
|
|
58
|
+
stroke="var(--icon-accent)"
|
|
59
|
+
strokeOpacity={0.15}
|
|
60
|
+
strokeWidth={2}
|
|
61
|
+
className="animate-pulse"
|
|
62
|
+
/>
|
|
63
|
+
</svg>
|
|
64
|
+
{showLabel ? (
|
|
65
|
+
<div
|
|
66
|
+
className="absolute inset-0 flex items-center justify-center text-[10px] sm:text-xs font-medium"
|
|
67
|
+
style={{ color: 'var(--text-main)' }}
|
|
68
|
+
>
|
|
69
|
+
{label ? label : `${Math.round(clamped)}%`}
|
|
70
|
+
</div>
|
|
71
|
+
) : null}
|
|
72
|
+
</div>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export default ProgressCircle
|
|
77
|
+
|
|
78
|
+
|