kyd-shared-badge 0.3.121 → 0.3.123
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/connect/ConnectAccounts.tsx +442 -453
- package/src/connect/types.ts +3 -0
- package/src/ui/connect-progress.tsx +130 -50
- package/src/ui/progress-circle.tsx +35 -12
|
@@ -3,9 +3,9 @@ import { useRouter } from 'next/navigation';
|
|
|
3
3
|
import { ProviderIcon } from '../utils/provider';
|
|
4
4
|
import { normalizeLinkedInInput } from './linkedin';
|
|
5
5
|
import type { ConnectAccountsProps } from './types';
|
|
6
|
-
import { CheckCircle, Link2, LinkIcon, Unlink, ArrowLeft, ExternalLink,
|
|
7
|
-
import { AnimatePresence, motion } from 'framer-motion';
|
|
8
|
-
import { Button, Input, Spinner, Card, CardHeader, CardContent,
|
|
6
|
+
import { CheckCircle, Link2, LinkIcon, Unlink, ArrowLeft, ExternalLink, Eye, Lock, InfoIcon } from 'lucide-react';
|
|
7
|
+
import { AnimatePresence, motion, useReducedMotion } from 'framer-motion';
|
|
8
|
+
import { Button, Input, Spinner, Card, CardHeader, CardContent, CardTitle, ConnectProgress } from '../ui';
|
|
9
9
|
import Link from 'next/link';
|
|
10
10
|
import { Tooltip, TooltipTrigger, TooltipProvider, TooltipContent } from '../ui/';
|
|
11
11
|
|
|
@@ -42,6 +42,9 @@ export function ConnectAccounts(props: ConnectAccountsProps) {
|
|
|
42
42
|
githubAppSlugId,
|
|
43
43
|
userId,
|
|
44
44
|
inviteId,
|
|
45
|
+
shouldShowContinueButton,
|
|
46
|
+
shouldDisableContinueButton,
|
|
47
|
+
onContinue,
|
|
45
48
|
} = props;
|
|
46
49
|
|
|
47
50
|
const router = useRouter();
|
|
@@ -49,8 +52,9 @@ export function ConnectAccounts(props: ConnectAccountsProps) {
|
|
|
49
52
|
const [linkUrl, setLinkUrl] = useState('');
|
|
50
53
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
51
54
|
const [isDisconnecting, setIsDisconnecting] = useState<string | null>(null);
|
|
52
|
-
const [showGithubManage, setShowGithubManage] = useState(false);
|
|
53
55
|
const [showDataHandling, setShowDataHandling] = useState(false);
|
|
56
|
+
const [previewProviderId, setPreviewProviderId] = useState<string | null>(null);
|
|
57
|
+
const [previewAction, setPreviewAction] = useState<'connect' | 'disconnect' | null>(null);
|
|
54
58
|
|
|
55
59
|
const apiBase = apiGatewayUrl || (typeof process !== 'undefined' ? (process.env.NEXT_PUBLIC_API_GATEWAY_URL as string) : '');
|
|
56
60
|
const connectedIds = useMemo(
|
|
@@ -63,8 +67,6 @@ export function ConnectAccounts(props: ConnectAccountsProps) {
|
|
|
63
67
|
useEffect(() => {
|
|
64
68
|
if (initialProviderId && initialProviderId !== selectedProviderId) {
|
|
65
69
|
setSelectedProviderIdAndCallback(initialProviderId);
|
|
66
|
-
// If we landed here from initialProviderId, show the existing card (not manage)
|
|
67
|
-
if (initialProviderId === 'githubapp') setShowGithubManage(false);
|
|
68
70
|
}
|
|
69
71
|
// Do not clear selection if initialProviderId becomes falsy later
|
|
70
72
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
@@ -114,7 +116,6 @@ export function ConnectAccounts(props: ConnectAccountsProps) {
|
|
|
114
116
|
});
|
|
115
117
|
const data = await res.json().catch(() => ({}));
|
|
116
118
|
if (!res.ok) throw new Error(data?.error || `Failed to disconnect ${providerId}.`);
|
|
117
|
-
setShowGithubManage(false);
|
|
118
119
|
if (onDisconnected) onDisconnected(providerId);
|
|
119
120
|
} catch (e) {
|
|
120
121
|
const err = e as Error;
|
|
@@ -194,11 +195,21 @@ export function ConnectAccounts(props: ConnectAccountsProps) {
|
|
|
194
195
|
) || null;
|
|
195
196
|
}, [selectedProviderId, providers]);
|
|
196
197
|
|
|
198
|
+
const setPreview = (providerId: string, action: 'connect' | 'disconnect') => {
|
|
199
|
+
setPreviewProviderId(providerId.toLowerCase());
|
|
200
|
+
setPreviewAction(action);
|
|
201
|
+
};
|
|
202
|
+
const clearPreview = () => {
|
|
203
|
+
setPreviewProviderId(null);
|
|
204
|
+
setPreviewAction(null);
|
|
205
|
+
};
|
|
206
|
+
|
|
197
207
|
const handleConnectBack = () => {
|
|
198
208
|
setSelectedProviderIdAndCallback(null);
|
|
199
209
|
setLinkUrl('');
|
|
200
|
-
setShowGithubManage(false);
|
|
201
210
|
setShowDataHandling(false);
|
|
211
|
+
setPreviewProviderId(null);
|
|
212
|
+
setPreviewAction(null);
|
|
202
213
|
};
|
|
203
214
|
|
|
204
215
|
const cardVariants = {
|
|
@@ -206,11 +217,18 @@ export function ConnectAccounts(props: ConnectAccountsProps) {
|
|
|
206
217
|
animate: { opacity: 1, y: 0 },
|
|
207
218
|
exit: { opacity: 0, y: -20 },
|
|
208
219
|
};
|
|
220
|
+
const shouldReduceMotion = useReducedMotion();
|
|
221
|
+
const ease = [0.22, 1, 0.36, 1] as const;
|
|
209
222
|
const fadeOnly = {
|
|
210
223
|
initial: { opacity: 0 },
|
|
211
224
|
animate: { opacity: 1 },
|
|
212
225
|
exit: { opacity: 0 },
|
|
213
226
|
};
|
|
227
|
+
const fadeIn = {
|
|
228
|
+
hidden: { opacity: 0, y: shouldReduceMotion ? 0 : 8 },
|
|
229
|
+
visible: { opacity: 1, y: 0, transition: { duration: 0.3, ease } },
|
|
230
|
+
exit: { opacity: 0, y: shouldReduceMotion ? 0 : -4, transition: { duration: 0.2, ease } }
|
|
231
|
+
};
|
|
214
232
|
|
|
215
233
|
// GitHub status helpers
|
|
216
234
|
const githubConnectedAccount = useMemo(() => {
|
|
@@ -223,484 +241,455 @@ export function ConnectAccounts(props: ConnectAccountsProps) {
|
|
|
223
241
|
}, [githubConnectedAccount]);
|
|
224
242
|
|
|
225
243
|
// Progress UI is outsourced to ConnectProgress
|
|
226
|
-
|
|
227
|
-
return (
|
|
228
|
-
<>
|
|
229
|
-
|
|
230
|
-
<AnimatePresence initial={false} mode="wait">
|
|
231
|
-
{showDataHandling ? (
|
|
232
|
-
<motion.div
|
|
233
|
-
key="data-handling-card"
|
|
234
|
-
className="rounded-xl border max-w-xl w-full"
|
|
235
|
-
initial="initial" animate="animate" exit="exit" variants={fadeOnly}
|
|
236
|
-
style={{ backgroundColor: 'var(--content-card-background)', borderColor: 'var(--icon-button-secondary)'}}
|
|
237
|
-
>
|
|
238
|
-
<div className="sm:p-6 p-4">
|
|
239
|
-
<button onClick={() => setShowDataHandling(false)} className="flex items-center gap-2 text-sm text-[var(--text-secondary)] hover:text-[var(--text-main)] transition-colors mb-4">
|
|
240
|
-
<ArrowLeft className="w-4 h-4" />
|
|
241
|
-
Back
|
|
242
|
-
</button>
|
|
243
|
-
<div className="text-center">
|
|
244
|
-
<div className="flex justify-center mb-4">
|
|
245
|
-
<InfoIcon className="w-10 h-10 text-[var(--text-main)]" />
|
|
246
|
-
</div>
|
|
247
|
-
<h3 className="sm:text-lg text-base font-semibold" style={{ color: 'var(--text-main)'}}>How your data is handled</h3>
|
|
248
|
-
<p className="sm:text-sm text-xs text-[var(--text-secondary)] mx-auto max-w-md mt-2 leading-relaxed">
|
|
249
|
-
We understand that giving access to your private repositories can be a bit scary. So here's the deal: We install the KYD GitHub App in your account. The app has
|
|
250
|
-
<span className="mx-1"><Link href="https://docs.github.com/en/rest/authentication/permissions-required-for-github-apps?apiVersion=2022-11-28#repository-permissions-for-contents" target="_blank" rel="noopener noreferrer" className="underline" style={{ color: 'var(--icon-accent)'}}>Contents <ExternalLink className="size-3 inline-block" /></Link></span>
|
|
251
|
-
read access - only to the repositories you select. Then, once you request a badge assessment, we read the repositories and analyze the code, then its deleted, forever. Your code is not accessible to anyone, not even us.
|
|
252
|
-
</p>
|
|
253
|
-
<p className="sm:text-sm text-xs text-[var(--text-secondary)] mx-auto max-w-md mt-3 leading-relaxed">
|
|
254
|
-
For other details, see our{' '}
|
|
255
|
-
<Link href="https://www.knowyourdeveloper.ai/privacy-policy" target="_blank" rel="noopener noreferrer" className="underline" style={{ color: 'var(--icon-accent)'}}>
|
|
256
|
-
Privacy Policy <ExternalLink className="size-3 inline-block ml-1" />
|
|
257
|
-
</Link>.
|
|
258
|
-
</p>
|
|
259
|
-
</div>
|
|
260
|
-
</div>
|
|
261
|
-
</motion.div>
|
|
262
|
-
) : selectedProvider && selectedProvider.id !== 'githubapp' ? (
|
|
263
|
-
<motion.div
|
|
264
|
-
key="connect-card"
|
|
265
|
-
initial="initial" animate="animate" exit="exit" variants={fadeOnly}
|
|
266
|
-
className="rounded-xl border max-w-xl w-full"
|
|
267
|
-
style={{ backgroundColor: 'var(--content-card-background)', borderColor: 'var(--icon-button-secondary)'}}
|
|
268
|
-
>
|
|
269
|
-
<div className="sm:p-6 p-4">
|
|
270
|
-
<button onClick={handleConnectBack} className="flex items-center gap-2 text-sm text-[var(--text-secondary)] hover:text-[var(--text-main)] transition-colors mb-4">
|
|
271
|
-
<ArrowLeft className="w-4 h-4" />
|
|
272
|
-
Back
|
|
273
|
-
</button>
|
|
274
|
-
<div className="text-center">
|
|
275
|
-
<div className="flex justify-center mb-4">
|
|
276
|
-
<ProviderIcon name={selectedProvider.id} className={`w-10 h-10 ${selectedProvider.iconColor || 'text-gray-500'}`} />
|
|
277
|
-
</div>
|
|
278
|
-
<h3 className="sm:text-lg text-base font-semibold" style={{ color: 'var(--text-main)'}}>
|
|
279
|
-
{selectedProvider.connectionType === 'url' || (selectedProvider.connectionType || 'url') === 'link'
|
|
280
|
-
? `Use Public ${selectedProvider.name} Profile`
|
|
281
|
-
: `Connect ${selectedProvider.name}`}
|
|
282
|
-
</h3>
|
|
283
|
-
<p className="sm:text-sm text-xs text-[var(--text-secondary)] mx-auto">
|
|
284
|
-
{(selectedProvider.connectionType === 'url' || selectedProvider.connectionType === 'link')
|
|
285
|
-
? (selectedProvider.placeholder || 'Enter your public profile URL.')
|
|
286
|
-
: `Authorize with ${selectedProvider.name} to connect your account.`}
|
|
287
|
-
</p>
|
|
288
|
-
</div>
|
|
289
|
-
|
|
290
|
-
{(selectedProvider.connectionType === 'url' || selectedProvider.connectionType === 'link') ? (
|
|
291
|
-
<motion.form
|
|
292
|
-
onSubmit={(e) => { e.preventDefault(); onSubmitLink(selectedProvider.id); }}
|
|
293
|
-
className="mt-6 space-y-4"
|
|
294
|
-
initial="initial" animate="animate" exit="exit" variants={cardVariants}
|
|
295
|
-
>
|
|
296
|
-
{selectedProvider.id === 'linkedin' && (
|
|
297
|
-
<>
|
|
298
|
-
<p className="sm:text-xs items-center text-[10px] text-[var(--text-secondary)] leading-relaxed max-w-xs mx-auto -mt-2">
|
|
299
|
-
<Link
|
|
300
|
-
href="https://www.linkedin.com/public-profile/settings"
|
|
301
|
-
target="_blank"
|
|
302
|
-
rel="noopener noreferrer"
|
|
303
|
-
className="underline"
|
|
304
|
-
style={{ color: 'var(--icon-accent)' }}
|
|
305
|
-
>
|
|
306
|
-
LinkedIn <ExternalLink className="size-3 inline-block ml-1 underline-0" />
|
|
307
|
-
</Link>
|
|
308
|
-
. This opens your public profile settings (you’ll see your shareable URL if you’re signed in).
|
|
309
|
-
</p>
|
|
310
|
-
<p className="sm:text-xs items-center text-[10px] text-[var(--text-secondary)] leading-relaxed max-w-xs mx-auto -mt-2">
|
|
311
|
-
LinkedIn data is not used to contribute to your score.
|
|
312
|
-
</p>
|
|
313
|
-
</>
|
|
314
244
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
</div>
|
|
330
|
-
<motion.div whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }}>
|
|
331
|
-
<Button type="submit" className="w-full bg-[var(--icon-accent)] text-white hover:bg-[var(--icon-accent-hover)] transition-colors" disabled={isSubmitting}>
|
|
332
|
-
{isSubmitting ? (
|
|
333
|
-
<div className="flex items-center justify-center">
|
|
334
|
-
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
|
|
335
|
-
Connecting...
|
|
336
|
-
</div>
|
|
337
|
-
) : (
|
|
338
|
-
'Connect'
|
|
339
|
-
)}
|
|
340
|
-
</Button>
|
|
341
|
-
</motion.div>
|
|
342
|
-
</motion.form>
|
|
343
|
-
) : (
|
|
344
|
-
<div className="mt-6">
|
|
345
|
-
<motion.div whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }}>
|
|
346
|
-
<Button onClick={() => onOAuth(selectedProvider.id)} className="w-full bg-[var(--icon-accent)] text-white hover:bg-[var(--icon-accent-hover)] transition-colors">
|
|
347
|
-
<ExternalLink className="w-4 h-4 mr-2" />
|
|
348
|
-
Connect with {selectedProvider.name}
|
|
349
|
-
</Button>
|
|
350
|
-
</motion.div>
|
|
351
|
-
</div>
|
|
352
|
-
)}
|
|
245
|
+
return (
|
|
246
|
+
<div className="w-full grid grid-cols-1 lg:grid-cols-[minmax(0,1fr)_380px] lg:justify-center gap-6 items-start max-w-7xl">
|
|
247
|
+
<div className="w-full flex justify-end">
|
|
248
|
+
<div className="w-full max-w-lg">
|
|
249
|
+
{/* Mobile: show progress above accounts, left-aligned */}
|
|
250
|
+
<div className="lg:hidden mb-4">
|
|
251
|
+
<ConnectProgress
|
|
252
|
+
layout="inline"
|
|
253
|
+
providers={providers}
|
|
254
|
+
connectedIds={connectedIds}
|
|
255
|
+
previewProviderId={previewProviderId}
|
|
256
|
+
previewAction={previewAction}
|
|
257
|
+
selectedProviderId={selectedProvider?.id || null}
|
|
258
|
+
/>
|
|
353
259
|
</div>
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
<motion.div className="p-6 flex flex-col items-center" initial="initial" animate="animate" exit="exit" variants={fadeOnly}>
|
|
366
|
-
<div className="w-full flex items-center gap-3 mb-2 justify-center">
|
|
367
|
-
<ProviderIcon name="github" className="w-8 h-8 inline-block" />
|
|
368
|
-
<span className="sm:text-xl text-base font-semibold text-[var(--text-main)]">Connect Private GitHub Repositories</span>
|
|
369
|
-
</div>
|
|
370
|
-
<p className="sm:text-sm text-xs text-[var(--text-secondary)] leading-relaxed mt-1 mb-6 text-center max-w-md">
|
|
371
|
-
You've successfully linked your GitHub account!
|
|
372
|
-
<br />
|
|
373
|
-
To complete your profile, you can optionally allow access to your <b>private repositories</b>. This is useful if you'd like to highlight private work or share additional contributions for verification.
|
|
374
|
-
<br /><br />
|
|
375
|
-
<span className="text-[var(--text-main)] font-medium">
|
|
376
|
-
Would you like to connect your private repositories?
|
|
377
|
-
</span>
|
|
378
|
-
<button
|
|
379
|
-
type="button"
|
|
380
|
-
onClick={() => setShowDataHandling(true)}
|
|
381
|
-
className="sm:text-sm text-xs underline text-[var(--icon-accent)] hover:text-[var(--icon-accent-hover)]"
|
|
382
|
-
>
|
|
383
|
-
How KYD Handles Your Data
|
|
260
|
+
{showDataHandling ? (
|
|
261
|
+
<motion.div
|
|
262
|
+
key="data-handling-card"
|
|
263
|
+
className="rounded-xl border max-w-xl w-full"
|
|
264
|
+
initial="initial" animate="animate" exit="exit" variants={fadeOnly}
|
|
265
|
+
style={{ backgroundColor: 'var(--content-card-background)', borderColor: 'var(--icon-button-secondary)'}}
|
|
266
|
+
>
|
|
267
|
+
<div className="sm:p-6 p-4">
|
|
268
|
+
<button onClick={() => setShowDataHandling(false)} className="flex items-center gap-2 text-sm text-[var(--text-secondary)] hover:text-[var(--text-main)] transition-colors mb-4">
|
|
269
|
+
<ArrowLeft className="w-4 h-4" />
|
|
270
|
+
Back
|
|
384
271
|
</button>
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
>
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
>
|
|
403
|
-
<span className="flex items-center justify-center">
|
|
404
|
-
<ExternalLink className="w-4 h-4 mr-2" />
|
|
405
|
-
Yes, connect my private repos
|
|
406
|
-
</span>
|
|
407
|
-
</Button>
|
|
408
|
-
</motion.div>
|
|
272
|
+
<div className="text-center">
|
|
273
|
+
<div className="flex justify-center mb-4">
|
|
274
|
+
<InfoIcon className="w-10 h-10 text-[var(--text-main)]" />
|
|
275
|
+
</div>
|
|
276
|
+
<h3 className="sm:text-lg text-base font-semibold" style={{ color: 'var(--text-main)'}}>How your data is handled</h3>
|
|
277
|
+
<p className="sm:text-sm text-xs text-[var(--text-secondary)] mx-auto max-w-md mt-2 leading-relaxed">
|
|
278
|
+
We understand that giving access to your private repositories can be a bit scary. So here's the deal: We install the KYD GitHub App in your account. The app has
|
|
279
|
+
<span className="mx-1"><Link href="https://docs.github.com/en/rest/authentication/permissions-required-for-github-apps?apiVersion=2022-11-28#repository-permissions-for-contents" target="_blank" rel="noopener noreferrer" className="underline" style={{ color: 'var(--icon-accent)'}}>Contents <ExternalLink className="size-3 inline-block" /></Link></span>
|
|
280
|
+
read access - only to the repositories you select. Then, once you request a badge assessment, we read the repositories and analyze the code, then its deleted, forever. Your code is not accessible to anyone, not even us.
|
|
281
|
+
</p>
|
|
282
|
+
<p className="sm:text-sm text-xs text-[var(--text-secondary)] mx-auto max-w-md mt-3 leading-relaxed">
|
|
283
|
+
For other details, see our{' '}
|
|
284
|
+
<Link href="https://www.knowyourdeveloper.ai/privacy-policy" target="_blank" rel="noopener noreferrer" className="underline" style={{ color: 'var(--icon-accent)'}}>
|
|
285
|
+
Privacy Policy <ExternalLink className="size-3 inline-block ml-1" />
|
|
286
|
+
</Link>.
|
|
287
|
+
</p>
|
|
288
|
+
</div>
|
|
409
289
|
</div>
|
|
410
290
|
</motion.div>
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
291
|
+
) : selectedProvider && selectedProvider.id !== 'githubapp' ? (
|
|
292
|
+
<motion.div
|
|
293
|
+
key="connect-card"
|
|
294
|
+
initial="initial" animate="animate" exit="exit" variants={fadeOnly}
|
|
295
|
+
className="rounded-xl border max-w-xl w-full"
|
|
296
|
+
style={{ backgroundColor: 'var(--content-card-background)', borderColor: 'var(--icon-button-secondary)'}}
|
|
297
|
+
>
|
|
298
|
+
<div className="sm:p-6 p-4">
|
|
299
|
+
<button onClick={handleConnectBack} className="flex items-center gap-2 text-sm text-[var(--text-secondary)] hover:text-[var(--text-main)] transition-colors mb-4">
|
|
300
|
+
<ArrowLeft className="w-4 h-4" />
|
|
301
|
+
Back
|
|
302
|
+
</button>
|
|
303
|
+
<div className="text-center">
|
|
304
|
+
<div className="flex justify-center mb-4">
|
|
305
|
+
<ProviderIcon name={selectedProvider.id} className={`w-10 h-10 ${selectedProvider.iconColor || 'text-gray-500'}`} />
|
|
306
|
+
</div>
|
|
307
|
+
<h3 className="sm:text-lg text-base font-semibold" style={{ color: 'var(--text-main)'}}>
|
|
308
|
+
{selectedProvider.connectionType === 'url' || (selectedProvider.connectionType || 'url') === 'link'
|
|
309
|
+
? `Use Public ${selectedProvider.name} Profile`
|
|
310
|
+
: `Connect ${selectedProvider.name}`}
|
|
311
|
+
</h3>
|
|
312
|
+
<p className="sm:text-sm text-xs text-[var(--text-secondary)] mx-auto">
|
|
313
|
+
{(selectedProvider.connectionType === 'url' || selectedProvider.connectionType === 'link')
|
|
314
|
+
? (selectedProvider.placeholder || 'Enter your public profile URL.')
|
|
315
|
+
: `Authorize with ${selectedProvider.name} to connect your account.`}
|
|
316
|
+
</p>
|
|
430
317
|
</div>
|
|
431
|
-
<h3 className="sm:text-lg text-base font-semibold" style={{ color: 'var(--text-main)'}}>Manage GitHub Connections</h3>
|
|
432
|
-
<p className="sm:text-sm text-xs text-[var(--text-secondary)] mx-auto max-w-md">
|
|
433
|
-
Connect or disconnect your GitHub OAuth account and optional GitHub App for private repositories.
|
|
434
|
-
</p>
|
|
435
|
-
</div>
|
|
436
318
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
<
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
)
|
|
319
|
+
{(selectedProvider.connectionType === 'url' || selectedProvider.connectionType === 'link') ? (
|
|
320
|
+
<motion.form
|
|
321
|
+
onSubmit={(e) => { e.preventDefault(); onSubmitLink(selectedProvider.id); }}
|
|
322
|
+
className="mt-6 space-y-4"
|
|
323
|
+
initial="initial" animate="animate" exit="exit" variants={cardVariants}
|
|
324
|
+
>
|
|
325
|
+
{selectedProvider.id === 'linkedin' && (
|
|
326
|
+
<>
|
|
327
|
+
<p className="sm:text-xs items-center text-[10px] text-[var(--text-secondary)] leading-relaxed max-w-xs mx-auto -mt-2">
|
|
328
|
+
<Link
|
|
329
|
+
href="https://www.linkedin.com/public-profile/settings"
|
|
330
|
+
target="_blank"
|
|
331
|
+
rel="noopener noreferrer"
|
|
332
|
+
className="underline"
|
|
333
|
+
style={{ color: 'var(--icon-accent)' }}
|
|
334
|
+
>
|
|
335
|
+
LinkedIn <ExternalLink className="size-3 inline-block ml-1 underline-0" />
|
|
336
|
+
</Link>
|
|
337
|
+
. This opens your public profile settings (you’ll see your shareable URL if you’re signed in).
|
|
338
|
+
</p>
|
|
339
|
+
<p className="sm:text-xs items-center text-[10px] text-[var(--text-secondary)] leading-relaxed max-w-xs mx-auto -mt-2">
|
|
340
|
+
LinkedIn data is not used to contribute to your score.
|
|
341
|
+
</p>
|
|
342
|
+
</>
|
|
343
|
+
|
|
344
|
+
)}
|
|
345
|
+
<div className="relative">
|
|
346
|
+
<LinkIcon className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-[var(--text-secondary)]" />
|
|
347
|
+
<Input
|
|
348
|
+
type="url"
|
|
349
|
+
value={linkUrl}
|
|
350
|
+
onChange={(e) => setLinkUrl(e.target.value)}
|
|
351
|
+
placeholder={selectedProvider.placeholder || 'https://example.com/your-profile'}
|
|
352
|
+
required
|
|
353
|
+
className="w-full border bg-transparent p-2 pl-9"
|
|
354
|
+
style={{ color: 'var(--text-main)', borderColor: 'var(--icon-button-secondary)'}}
|
|
355
|
+
onPaste={selectedProvider.id === 'linkedin' ? (e) => { const text = e.clipboardData.getData('text'); setLinkUrl(normalizeLinkedInInput(text)); e.preventDefault(); } : undefined}
|
|
356
|
+
onBlur={selectedProvider.id === 'linkedin' ? (() => setLinkUrl(normalizeLinkedInInput(linkUrl))) : undefined}
|
|
357
|
+
/>
|
|
465
358
|
</div>
|
|
359
|
+
<motion.div whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }}>
|
|
360
|
+
<Button type="submit" className="w-full bg-[var(--icon-accent)] text-white hover:bg-[var(--icon-accent-hover)] transition-colors" disabled={isSubmitting}>
|
|
361
|
+
{isSubmitting ? (
|
|
362
|
+
<div className="flex items-center justify-center">
|
|
363
|
+
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
|
|
364
|
+
Connecting...
|
|
365
|
+
</div>
|
|
366
|
+
) : (
|
|
367
|
+
'Connect'
|
|
368
|
+
)}
|
|
369
|
+
</Button>
|
|
370
|
+
</motion.div>
|
|
371
|
+
</motion.form>
|
|
372
|
+
) : (
|
|
373
|
+
<div className="mt-6">
|
|
374
|
+
<motion.div whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }}>
|
|
375
|
+
<Button onClick={() => onOAuth(selectedProvider.id)} className="w-full bg-[var(--icon-accent)] text-white hover:bg-[var(--icon-accent-hover)] transition-colors">
|
|
376
|
+
<ExternalLink className="w-4 h-4 mr-2" />
|
|
377
|
+
Connect with {selectedProvider.name}
|
|
378
|
+
</Button>
|
|
379
|
+
</motion.div>
|
|
466
380
|
</div>
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
381
|
+
)}
|
|
382
|
+
</div>
|
|
383
|
+
</motion.div>
|
|
384
|
+
) : selectedProvider && selectedProvider.id === 'githubapp' ? (
|
|
385
|
+
<div
|
|
386
|
+
key="github-card"
|
|
387
|
+
className="rounded-xl border max-w-xl w-full"
|
|
388
|
+
style={{
|
|
389
|
+
backgroundColor: 'var(--content-card-background)',
|
|
390
|
+
borderColor: 'var(--icon-button-secondary)',
|
|
391
|
+
}}
|
|
392
|
+
>
|
|
393
|
+
<motion.div className="p-6 flex flex-col items-center" initial="initial" animate="animate" exit="exit" variants={fadeOnly}>
|
|
394
|
+
<div className="w-full flex items-center gap-3 mb-2 justify-center">
|
|
395
|
+
<ProviderIcon name="github" className="w-8 h-8 inline-block" />
|
|
396
|
+
<span className="sm:text-xl text-base font-semibold text-[var(--text-main)]">Connect Private GitHub Repositories</span>
|
|
397
|
+
</div>
|
|
398
|
+
<p className="sm:text-sm text-xs text-[var(--text-secondary)] leading-relaxed mt-1 mb-6 text-center max-w-md">
|
|
399
|
+
You've successfully linked your GitHub account!
|
|
400
|
+
<br />
|
|
401
|
+
To complete your profile, you can optionally allow access to your <b>private repositories</b>. This is useful if you'd like to highlight private work or share additional contributions for verification.
|
|
402
|
+
<br /><br />
|
|
403
|
+
<span className="text-[var(--text-main)] font-medium">
|
|
404
|
+
Would you like to connect your private repositories?
|
|
405
|
+
</span>
|
|
406
|
+
<button
|
|
407
|
+
type="button"
|
|
408
|
+
onClick={() => setShowDataHandling(true)}
|
|
409
|
+
className="sm:text-sm text-xs underline text-[var(--icon-accent)] hover:text-[var(--icon-accent-hover)]"
|
|
410
|
+
>
|
|
411
|
+
How KYD Handles Your Data
|
|
412
|
+
</button>
|
|
413
|
+
</p>
|
|
414
|
+
<div className="flex flex-col sm:flex-row w-full gap-3 mt-2 justify-center items-center">
|
|
415
|
+
<motion.div whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }}>
|
|
473
416
|
<Button
|
|
474
|
-
className="
|
|
475
|
-
|
|
476
|
-
|
|
417
|
+
className="w-full sm:w-auto text-[var(--text-main)] transition-colors border border-[var(--icon-button-secondary)]"
|
|
418
|
+
variant="destructive"
|
|
419
|
+
onClick={() => {
|
|
420
|
+
handleConnectBack();
|
|
421
|
+
}}
|
|
477
422
|
>
|
|
478
|
-
|
|
423
|
+
No, don't connect
|
|
479
424
|
</Button>
|
|
480
|
-
</div>
|
|
481
|
-
<div
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
variant="destructive"
|
|
488
|
-
>
|
|
489
|
-
<Unlink className="size-3 sm:size-4" />
|
|
490
|
-
<span>Uninstall</span>
|
|
491
|
-
</Button>
|
|
492
|
-
) : (
|
|
493
|
-
<Button
|
|
494
|
-
onClick={onGithubAppInstall}
|
|
495
|
-
className="bg-[var(--icon-accent)] text-white hover:bg-[var(--icon-accent-hover)]"
|
|
496
|
-
>
|
|
425
|
+
</motion.div>
|
|
426
|
+
<motion.div whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }}>
|
|
427
|
+
<Button
|
|
428
|
+
className="w-full sm:w-auto bg-[var(--icon-accent)] text-white transition-colors font-semibold"
|
|
429
|
+
onClick={onGithubAppInstall}
|
|
430
|
+
>
|
|
431
|
+
<span className="flex items-center justify-center">
|
|
497
432
|
<ExternalLink className="w-4 h-4 mr-2" />
|
|
498
|
-
|
|
499
|
-
</
|
|
500
|
-
|
|
501
|
-
</div>
|
|
433
|
+
Yes, connect my private repos
|
|
434
|
+
</span>
|
|
435
|
+
</Button>
|
|
436
|
+
</motion.div>
|
|
502
437
|
</div>
|
|
503
|
-
</div>
|
|
438
|
+
</motion.div>
|
|
504
439
|
</div>
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
<
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
<
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
return (
|
|
558
|
-
<div key={providerId} className="group flex items-center justify-between p-2 rounded-lg transition-colors hover:bg-[var(--icon-button-secondary)]/10 transform transition-transform hover:-translate-y-px">
|
|
559
|
-
<div className="flex items-center gap-3">
|
|
560
|
-
|
|
561
|
-
{isConnected && connectedUrl ? (
|
|
562
|
-
<Link href={connectedUrl} target="_blank" rel="noopener noreferrer" className="flex items-center gap-3 hover:underline">
|
|
563
|
-
<ProviderIcon name={provider.id} className={`sm:size-7 size-5 ${provider.iconColor || 'text-gray-500'}`} />
|
|
564
|
-
<span className="font-medium sm:text-base text-sm" style={{ color: 'var(--text-main)'}}>{provider.name}</span>
|
|
565
|
-
{isRequired && !isConnected ? (requiredFlag()) : null}
|
|
566
|
-
</Link>
|
|
567
|
-
) : (
|
|
440
|
+
) : (
|
|
441
|
+
<Card className="border-[var(--icon-button-secondary)] pt-2" style={{ backgroundColor: 'var(--content-card-background)'}}>
|
|
442
|
+
<AnimatePresence mode="wait">
|
|
443
|
+
<motion.div key="platform-list-shared" variants={cardVariants} initial="initial" animate="animate" exit="exit" transition={{ duration: 0.3 }}>
|
|
444
|
+
<CardHeader className="pb-4">
|
|
445
|
+
{handleBackButton && (
|
|
446
|
+
<button onClick={() => handleBackButton()} className="flex items-center gap-2 text-sm mb-4 text-[var(--text-secondary)] hover:text-[var(--text-main)] transition-colors">
|
|
447
|
+
<ArrowLeft className="w-4 h-4" /> Back
|
|
448
|
+
</button>
|
|
449
|
+
)}
|
|
450
|
+
<CardTitle className="sm:text-xl text-base font-semibold text-[var(--text-main)] mb-2">{headerTitle}</CardTitle>
|
|
451
|
+
<p className="sm:text-sm text-xs text-[var(--text-secondary)] leading-relaxed">{headerDescription}</p>
|
|
452
|
+
</CardHeader>
|
|
453
|
+
<CardContent className="space-y-2">
|
|
454
|
+
{(() => {
|
|
455
|
+
const oauthList = list.filter(p => p.connectionType === 'oauth');
|
|
456
|
+
const urlList = list.filter(p => (p.connectionType || 'url') === 'url' || p.connectionType === 'link');
|
|
457
|
+
|
|
458
|
+
const Row = (provider: typeof list[number]) => {
|
|
459
|
+
const providerId = provider.id;
|
|
460
|
+
const isRequired = requiredProviders?.includes(providerId);
|
|
461
|
+
const isConnected = connectedIds.has(providerId.toLowerCase());
|
|
462
|
+
const needsReconnect = reconnectIds.has(providerId.toLowerCase());
|
|
463
|
+
const isOauth = provider.connectionType === 'oauth'
|
|
464
|
+
const connectedUrl = connected.find(c => c.name.toLowerCase() === providerId.toLowerCase())?.url;
|
|
465
|
+
const betaFlag = () => (
|
|
466
|
+
<span
|
|
467
|
+
className="ml-2 inline-block rounded-full px-2 py-0.5 text-xs font-semibold bg-blue-100 text-blue-700 border border-blue-200"
|
|
468
|
+
style={{ verticalAlign: 'middle', letterSpacing: '0.05em' }}
|
|
469
|
+
>
|
|
470
|
+
Beta
|
|
471
|
+
</span>
|
|
472
|
+
);
|
|
473
|
+
|
|
474
|
+
const requiredFlag = () => (
|
|
475
|
+
<TooltipProvider>
|
|
476
|
+
<Tooltip>
|
|
477
|
+
<TooltipTrigger>
|
|
478
|
+
<div className="flex items-center justify-center size-4 sm:size-5 rounded-full bg-[var(--icon-accent)] cursor-help">
|
|
479
|
+
<span className="text-white text-xs font-bold -translate-y-px select-none">!</span>
|
|
480
|
+
</div>
|
|
481
|
+
</TooltipTrigger>
|
|
482
|
+
<TooltipContent>
|
|
483
|
+
<p>Required by <span className="font-semibold">{companyName || 'your recruiter'}</span></p>
|
|
484
|
+
</TooltipContent>
|
|
485
|
+
</Tooltip>
|
|
486
|
+
</TooltipProvider>
|
|
487
|
+
);
|
|
488
|
+
|
|
489
|
+
return (
|
|
490
|
+
<div key={providerId} className="group flex items-center justify-between p-2 rounded-lg transition-colors hover:bg-[var(--icon-button-secondary)]/10 transform transition-transform hover:-translate-y-px">
|
|
568
491
|
<div className="flex items-center gap-3">
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
492
|
+
|
|
493
|
+
{isConnected && connectedUrl ? (
|
|
494
|
+
<Link href={connectedUrl} target="_blank" rel="noopener noreferrer" className="flex items-center gap-3 hover:underline">
|
|
495
|
+
<ProviderIcon name={provider.id} className={`sm:size-7 size-5 ${provider.iconColor || 'text-gray-500'}`} />
|
|
496
|
+
<span className="font-medium sm:text-base text-sm" style={{ color: 'var(--text-main)'}}>{provider.name}</span>
|
|
497
|
+
{isRequired && !isConnected ? (requiredFlag()) : null}
|
|
498
|
+
</Link>
|
|
499
|
+
) : (
|
|
500
|
+
<div className="flex items-center gap-3">
|
|
501
|
+
<ProviderIcon name={provider.id} className={`sm:size-7 size-5 ${provider.iconColor || 'text-gray-500'}`} />
|
|
502
|
+
<span className="font-medium sm:text-base text-sm" style={{ color: 'var(--text-main)'}}>{provider.name}</span>
|
|
503
|
+
{isRequired && !isConnected ? (requiredFlag()) : null}
|
|
504
|
+
</div>
|
|
505
|
+
)}
|
|
506
|
+
{provider.beta ? betaFlag() : null}
|
|
572
507
|
</div>
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
508
|
+
|
|
509
|
+
{providerId.toLowerCase() === 'github' ? (
|
|
510
|
+
<div className="flex items-center gap-2">
|
|
511
|
+
{/* Public (OAuth) */}
|
|
512
|
+
{isGithubConnected ? (
|
|
513
|
+
<div className="relative flex items-center">
|
|
514
|
+
<div className="flex items-center gap-2 transition-opacity group-hover:opacity-0" style={{ color: 'var(--success-green)'}}>
|
|
515
|
+
<CheckCircle className="size-3 sm:size-4" />
|
|
516
|
+
<span className="text-sm font-medium">Connected</span>
|
|
517
|
+
</div>
|
|
518
|
+
<div className="absolute right-0 opacity-0 transition-opacity group-hover:opacity-100">
|
|
519
|
+
<button
|
|
520
|
+
onClick={() => onDisconnect('github')}
|
|
521
|
+
onMouseEnter={() => setPreview('github', 'disconnect')}
|
|
522
|
+
onMouseLeave={clearPreview}
|
|
523
|
+
className="inline-flex items-center gap-1.5 py-1.5 text-sm text-red-600 hover:text-red-700 hover:underline"
|
|
524
|
+
disabled={isDisconnecting === 'github'}
|
|
525
|
+
>
|
|
526
|
+
{isDisconnecting === 'github' ? <Spinner /> : <Unlink className="size-3 sm:size-4" />}
|
|
527
|
+
<span>Disconnect</span>
|
|
528
|
+
</button>
|
|
529
|
+
</div>
|
|
530
|
+
</div>
|
|
531
|
+
) : (
|
|
532
|
+
<motion.div whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }}>
|
|
533
|
+
<Button
|
|
534
|
+
onClick={() => setSelectedProviderIdAndCallback('github')}
|
|
535
|
+
onMouseEnter={() => setPreview('github', 'connect')}
|
|
536
|
+
onMouseLeave={clearPreview}
|
|
537
|
+
className="bg-[var(--icon-button-secondary)] text-[var(--text-main)] hover:bg-[var(--icon-accent)] hover:text-white border-0 sm:px-4 px-3 py-1 sm:py-2 rounded-lg flex items-center gap-2"
|
|
538
|
+
>
|
|
539
|
+
<Eye className="size-3 sm:size-4" />
|
|
540
|
+
<span className="sm:text-base text-sm">Connect</span>
|
|
541
|
+
</Button>
|
|
542
|
+
</motion.div>
|
|
543
|
+
)}
|
|
544
|
+
|
|
545
|
+
{/* Private (GitHub App) */}
|
|
546
|
+
{isGithubAppInstalled ? (
|
|
547
|
+
<div className="relative flex items-center">
|
|
548
|
+
<div className="flex items-center gap-2 transition-opacity group-hover:opacity-0" style={{ color: 'var(--success-green)'}}>
|
|
549
|
+
<CheckCircle className="size-3 sm:size-4" />
|
|
550
|
+
<span className="text-sm font-medium">Connected</span>
|
|
551
|
+
</div>
|
|
552
|
+
<div className="absolute right-0 opacity-0 transition-opacity group-hover:opacity-100">
|
|
553
|
+
<button
|
|
554
|
+
onClick={() => { window.location.href = 'https://github.com/settings/installations'; }}
|
|
555
|
+
className="inline-flex items-center gap-1.5 py-1.5 text-sm text-red-600 hover:text-red-700 hover:underline"
|
|
556
|
+
>
|
|
557
|
+
<Unlink className="size-3 sm:size-4" />
|
|
558
|
+
<span>Uninstall</span>
|
|
559
|
+
</button>
|
|
560
|
+
</div>
|
|
561
|
+
</div>
|
|
562
|
+
) : (
|
|
563
|
+
<motion.div whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }}>
|
|
564
|
+
<Button
|
|
565
|
+
onClick={() => setSelectedProviderIdAndCallback('githubapp')}
|
|
566
|
+
onMouseEnter={() => setPreview('githubapp', 'connect')}
|
|
567
|
+
onMouseLeave={clearPreview}
|
|
568
|
+
className="bg-[var(--icon-button-secondary)] text-[var(--text-main)] hover:bg-[var(--icon-accent)] hover:text-white border-0 sm:px-4 px-3 py-1 sm:py-2 rounded-lg flex items-center gap-2"
|
|
569
|
+
>
|
|
570
|
+
<Lock className="size-3 sm:size-4" />
|
|
571
|
+
<span className="sm:text-base text-sm">Connect</span>
|
|
572
|
+
</Button>
|
|
573
|
+
</motion.div>
|
|
574
|
+
)}
|
|
575
|
+
</div>
|
|
576
|
+
) : isConnected && isDisconnecting !== providerId ? (
|
|
577
|
+
<div className="relative flex items-center">
|
|
603
578
|
<div className="flex items-center gap-2 transition-opacity group-hover:opacity-0" style={{ color: 'var(--success-green)'}}>
|
|
604
579
|
<CheckCircle className="size-3 sm:size-4" />
|
|
605
580
|
<span className="text-sm font-medium">Connected</span>
|
|
606
581
|
</div>
|
|
607
582
|
<div className="absolute right-0 opacity-0 transition-opacity group-hover:opacity-100">
|
|
608
|
-
<
|
|
583
|
+
<button
|
|
584
|
+
onClick={() => onDisconnect(providerId)}
|
|
585
|
+
onMouseEnter={() => setPreview(providerId, 'disconnect')}
|
|
586
|
+
onMouseLeave={clearPreview}
|
|
587
|
+
className="inline-flex items-center gap-1.5 py-1.5 text-sm text-red-600 hover:text-red-700 hover:underline"
|
|
588
|
+
>
|
|
609
589
|
<Unlink className="size-3 sm:size-4" />
|
|
610
590
|
<span>Disconnect</span>
|
|
611
|
-
</
|
|
591
|
+
</button>
|
|
612
592
|
</div>
|
|
613
593
|
</div>
|
|
594
|
+
) : isDisconnecting === providerId ? (
|
|
595
|
+
<div className="relative flex items-center">
|
|
596
|
+
<Spinner />
|
|
597
|
+
</div>
|
|
598
|
+
) : (
|
|
599
|
+
<div className="flex items-center gap-2">
|
|
600
|
+
<>
|
|
601
|
+
<Button
|
|
602
|
+
onClick={() => setSelectedProviderIdAndCallback(providerId)}
|
|
603
|
+
onMouseEnter={() => setPreview(providerId, 'connect')}
|
|
604
|
+
onMouseLeave={clearPreview}
|
|
605
|
+
className="bg-[var(--icon-button-secondary)] text-[var(--text-main)] hover:bg-[var(--icon-accent)] hover:text-white border-0 sm:px-4 px-3 py-1 sm:py-2 rounded-lg flex items-center gap-2"
|
|
606
|
+
>
|
|
607
|
+
{isOauth ? (
|
|
608
|
+
<Link2 className="size-3 sm:size-4" />
|
|
609
|
+
) : (
|
|
610
|
+
<LinkIcon className="size-3 sm:size-4" />
|
|
611
|
+
)}
|
|
612
|
+
<span className="sm:text-base text-sm">Connect</span>
|
|
613
|
+
</Button>
|
|
614
|
+
{needsReconnect && !isRequired && (
|
|
615
|
+
<Button
|
|
616
|
+
onClick={() => onDisconnect(providerId)}
|
|
617
|
+
onMouseEnter={() => setPreview(providerId, 'disconnect')}
|
|
618
|
+
onMouseLeave={clearPreview}
|
|
619
|
+
className="inline-flex items-center justify-center gap-1.5 px-4 py-2 text-sm rounded border"
|
|
620
|
+
style={{ color: 'var(--text-main)', borderColor: 'var(--icon-button-secondary)'}}
|
|
621
|
+
variant="destructive"
|
|
622
|
+
>
|
|
623
|
+
<Unlink className="size-3 sm:size-4" />
|
|
624
|
+
<span>Remove</span>
|
|
625
|
+
</Button>
|
|
626
|
+
)}
|
|
627
|
+
</>
|
|
628
|
+
</div>
|
|
614
629
|
)}
|
|
615
|
-
{isDisconnecting === providerId && (
|
|
616
|
-
<Spinner />
|
|
617
|
-
)}
|
|
618
|
-
</div>
|
|
619
|
-
) : isConnected && isDisconnecting !== providerId ? (
|
|
620
|
-
<div className="relative flex items-center">
|
|
621
|
-
<div className="flex items-center gap-2 transition-opacity group-hover:opacity-0" style={{ color: 'var(--success-green)'}}>
|
|
622
|
-
<CheckCircle className="size-3 sm:size-4" />
|
|
623
|
-
<span className="text-sm font-medium">Connected</span>
|
|
624
|
-
</div>
|
|
625
|
-
<div className="absolute right-0 opacity-0 transition-opacity group-hover:opacity-100">
|
|
626
|
-
<button
|
|
627
|
-
onClick={() => onDisconnect(providerId)}
|
|
628
|
-
className="inline-flex items-center gap-1.5 py-1.5 text-sm text-red-600 hover:text-red-700 hover:underline"
|
|
629
|
-
>
|
|
630
|
-
<Unlink className="size-3 sm:size-4" />
|
|
631
|
-
<span>Disconnect</span>
|
|
632
|
-
</button>
|
|
633
|
-
</div>
|
|
634
630
|
</div>
|
|
635
|
-
)
|
|
636
|
-
|
|
637
|
-
<Spinner />
|
|
638
|
-
</div>
|
|
639
|
-
) : (
|
|
640
|
-
<div className="flex items-center gap-2">
|
|
641
|
-
<>
|
|
642
|
-
<Button
|
|
643
|
-
onClick={() => setSelectedProviderIdAndCallback(providerId)}
|
|
644
|
-
className="bg-[var(--icon-button-secondary)] text-[var(--text-main)] hover:bg-[var(--icon-accent)] hover:text-white border-0 sm:px-4 px-3 py-1 sm:py-2 rounded-lg flex items-center gap-2"
|
|
645
|
-
>
|
|
646
|
-
{isOauth ? (
|
|
647
|
-
<Link2 className="size-3 sm:size-4" />
|
|
648
|
-
) : (
|
|
649
|
-
<LinkIcon className="size-3 sm:size-4" />
|
|
650
|
-
)}
|
|
651
|
-
<span className="sm:text-base text-sm">Connect</span>
|
|
652
|
-
</Button>
|
|
653
|
-
{needsReconnect && !isRequired && (
|
|
654
|
-
<Button
|
|
655
|
-
onClick={() => onDisconnect(providerId)}
|
|
656
|
-
className="inline-flex items-center justify-center gap-1.5 px-4 py-2 text-sm rounded border"
|
|
657
|
-
style={{ color: 'var(--text-main)', borderColor: 'var(--icon-button-secondary)'}}
|
|
658
|
-
variant="destructive"
|
|
659
|
-
>
|
|
660
|
-
<Unlink className="size-3 sm:size-4" />
|
|
661
|
-
<span>Remove</span>
|
|
662
|
-
</Button>
|
|
663
|
-
)}
|
|
664
|
-
</>
|
|
665
|
-
</div>
|
|
666
|
-
)}
|
|
667
|
-
</div>
|
|
668
|
-
);
|
|
669
|
-
};
|
|
631
|
+
);
|
|
632
|
+
};
|
|
670
633
|
|
|
671
|
-
|
|
672
|
-
<>
|
|
673
|
-
{oauthList.length ? (
|
|
634
|
+
return (
|
|
674
635
|
<>
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
636
|
+
{oauthList.length ? (
|
|
637
|
+
<>
|
|
638
|
+
<div className="flex items-center my-2">
|
|
639
|
+
<div className="flex-1 border-t" style={{ borderColor: 'var(--icon-button-secondary)', opacity: 0.3 }} />
|
|
640
|
+
<span className="mx-3 text-[10px] sm:text-xs uppercase tracking-wide" style={{ color: 'var(--text-secondary)'}}>OAuth</span>
|
|
641
|
+
<div className="flex-1 border-t" style={{ borderColor: 'var(--icon-button-secondary)', opacity: 0.3 }} />
|
|
642
|
+
</div>
|
|
643
|
+
{oauthList.map(p => Row(p))}
|
|
644
|
+
</>
|
|
645
|
+
) : null}
|
|
683
646
|
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
647
|
+
{urlList.length ? (
|
|
648
|
+
<>
|
|
649
|
+
<div className="flex items-center my-2">
|
|
650
|
+
<div className="flex-1 border-t" style={{ borderColor: 'var(--icon-button-secondary)', opacity: 0.3 }} />
|
|
651
|
+
<span className="mx-3 text-[10px] sm:text-xs uppercase tracking-wide" style={{ color: 'var(--text-secondary)'}}>Public Urls</span>
|
|
652
|
+
<div className="flex-1 border-t" style={{ borderColor: 'var(--icon-button-secondary)', opacity: 0.3 }} />
|
|
653
|
+
</div>
|
|
654
|
+
{urlList.map(p => Row(p))}
|
|
655
|
+
</>
|
|
656
|
+
) : null}
|
|
692
657
|
</>
|
|
693
|
-
)
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
</
|
|
658
|
+
);
|
|
659
|
+
})()}
|
|
660
|
+
</CardContent>
|
|
661
|
+
</motion.div>
|
|
662
|
+
</AnimatePresence>
|
|
663
|
+
</Card>
|
|
664
|
+
)}
|
|
665
|
+
{shouldShowContinueButton && (
|
|
666
|
+
<motion.div className="mt-6 w-full" initial="hidden" animate="visible" variants={fadeIn}>
|
|
667
|
+
<motion.div whileHover={{ scale: 1.01 }} whileTap={{ scale: 0.99 }}>
|
|
668
|
+
<Button
|
|
669
|
+
onClick={onContinue}
|
|
670
|
+
disabled={shouldDisableContinueButton}
|
|
671
|
+
className="w-full text-white font-medium py-2.5 bg-[var(--icon-accent)] disabled:opacity-50"
|
|
672
|
+
>
|
|
673
|
+
Continue
|
|
674
|
+
</Button>
|
|
675
|
+
</motion.div>
|
|
698
676
|
</motion.div>
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
677
|
+
)}
|
|
678
|
+
</div>
|
|
679
|
+
</div>
|
|
680
|
+
|
|
681
|
+
{/* Desktop: progress card to the right, sticky while scrolling */}
|
|
682
|
+
<div className="hidden lg:block lg:sticky lg:top-6">
|
|
683
|
+
<ConnectProgress
|
|
684
|
+
layout="inline"
|
|
685
|
+
providers={providers}
|
|
686
|
+
connectedIds={connectedIds}
|
|
687
|
+
previewProviderId={previewProviderId}
|
|
688
|
+
previewAction={previewAction}
|
|
689
|
+
selectedProviderId={selectedProvider?.id || null}
|
|
690
|
+
/>
|
|
691
|
+
</div>
|
|
692
|
+
</div>
|
|
704
693
|
);
|
|
705
694
|
}
|
|
706
695
|
|