-
Notifications
You must be signed in to change notification settings - Fork 707
Expand file tree
/
Copy pathConnectAccountModal.tsx
More file actions
106 lines (98 loc) · 3.49 KB
/
Copy pathConnectAccountModal.tsx
File metadata and controls
106 lines (98 loc) · 3.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
import { useState, useEffect } from 'react'
import { Dialog, Text, Loader, useKumoToastManager } from '@cloudflare/kumo'
import { RpcStub } from 'capnweb'
import { AuthenticatedApi, GatekeeperVendorFilter } from '@gadgets/workshop-shared/api'
import { VendorDescription } from '@gadgets/workshop-shared/gatekeeper'
import VendorCard from './VendorCard'
import { withDoResetRetry } from './rpcErrors'
interface ConnectAccountModalProps {
visible: boolean
onCancel: () => void
onInitiated: () => void
authenticatedApi: RpcStub<AuthenticatedApi>
/** Optional filter to only show vendors supporting certain features */
filter?: GatekeeperVendorFilter
}
interface VendorOption {
id: string
description: VendorDescription
}
export default function ConnectAccountModal({
visible,
onCancel,
onInitiated,
authenticatedApi,
filter,
}: ConnectAccountModalProps) {
const toasts = useKumoToastManager()
const [connecting, setConnecting] = useState<string | null>(null)
const [vendors, setVendors] = useState<VendorOption[]>([])
const [vendorsLoading, setVendorsLoading] = useState(true)
// Fetch vendors when modal opens
useEffect(() => {
if (!visible) {
setConnecting(null)
return
}
const fetchVendors = async () => {
setVendorsLoading(true)
try {
const vendorList = await withDoResetRetry(() => authenticatedApi.listGatekeeperVendors(filter))
const unavailable = vendorList.filter(v => v.unavailable)
if (unavailable.length > 0) {
toasts.add({
title: `Some services are temporarily unavailable: ${unavailable.map(v => v.id).join(', ')}`,
variant: 'warning',
})
}
setVendors(vendorList.filter(v => !v.unavailable).map(v => ({ id: v.id, description: v.description })))
} catch (error) {
console.error('Failed to fetch vendors:', error)
toasts.add({ title: 'Failed to load available services', variant: 'error' })
} finally {
setVendorsLoading(false)
}
}
fetchVendors()
}, [visible, authenticatedApi, filter])
const handleConnect = async (vendorId: string) => {
setConnecting(vendorId)
try {
const result = await authenticatedApi.connectAccount(vendorId)
window.open(result.url, '_blank', 'noopener,noreferrer')
onInitiated()
} catch (error) {
console.error('Failed to initiate connection:', error)
toasts.add({ title: 'Failed to start connection flow', variant: 'error' })
setConnecting(null)
}
}
return (
<Dialog.Root open={visible} onOpenChange={(open) => { if (!open) onCancel() }}>
<Dialog className="p-6" size="base">
<Dialog.Title className="text-lg font-semibold mb-4">Connect Account</Dialog.Title>
{vendorsLoading ? (
<div className="text-center py-8">
<Loader />
</div>
) : vendors.length === 0 ? (
<div className="text-center py-8">
<Text variant="secondary">No services available to connect.</Text>
</div>
) : (
<div className="flex flex-col gap-3 mt-2">
{vendors.map(vendor => (
<VendorCard
key={vendor.id}
vendor={vendor.description}
onClick={() => handleConnect(vendor.id)}
loading={connecting === vendor.id}
disabled={connecting !== null && connecting !== vendor.id}
/>
))}
</div>
)}
</Dialog>
</Dialog.Root>
)
}