-
{name}
- {connected && (
-
-
- Connected
-
- )}
+
+
+
+
{icon}
+
+
+
{name}
+ {connected && (
+
+
+ Connected
+
+ )}
+
+
{detail || description}
-
{detail || description}
+ {connected ? (
+
+ ) : (
+
+ )}
- {connected ? (
-
- ) : (
-
+ {children}
+
+ )
+}
+
+function SecurityNote({ text }: { text: string }) {
+ return (
+
+ )
+}
+
+function StatusDot({ status }: { status?: string }) {
+ const color =
+ status === 'active' ? 'bg-green-400' :
+ status === 'syncing' ? 'bg-yellow-400 animate-pulse' :
+ status === 'error' ? 'bg-red-400' :
+ 'bg-neutral-500'
+
+ const label =
+ status === 'active' ? 'Connected' :
+ status === 'syncing' ? 'Syncing' :
+ status === 'error' ? 'Error' :
+ 'Unknown'
+
+ return (
+
+
+ {label}
+
+ )
+}
+
+function GSCDetails({ gscStatus }: { gscStatus: { connected: boolean; google_email?: string; gsc_property?: string; status?: string; last_synced_at?: string | null; error_message?: string | null } }) {
+ if (!gscStatus.connected) return null
+
+ const rows = [
+ { label: 'Status', value:
},
+ { label: 'Google Account', value: gscStatus.google_email || 'Unknown' },
+ { label: 'GSC Property', value: gscStatus.gsc_property || 'Unknown' },
+ { label: 'Last Synced', value: gscStatus.last_synced_at ? formatDateTime(new Date(gscStatus.last_synced_at)) : 'Never' },
+ ]
+
+ return (
+
+
+ {rows.map(row => (
+
+ {row.label}
+ {row.value}
+
+ ))}
+
+ {gscStatus.error_message && (
+
+
{gscStatus.error_message}
+
)}
)
}
+function BunnySetupForm({ siteId, onConnected }: { siteId: string; onConnected: () => void }) {
+ const [apiKey, setApiKey] = useState('')
+ const [pullZones, setPullZones] = useState
([])
+ const [selectedZone, setSelectedZone] = useState(null)
+ const [loadingZones, setLoadingZones] = useState(false)
+ const [connecting, setConnecting] = useState(false)
+ const [zonesLoaded, setZonesLoaded] = useState(false)
+
+ const handleLoadZones = async () => {
+ if (!apiKey.trim()) {
+ toast.error('Please enter your BunnyCDN API key')
+ return
+ }
+ setLoadingZones(true)
+ try {
+ const data = await getBunnyPullZones(siteId, apiKey.trim())
+ setPullZones(data.pull_zones || [])
+ setSelectedZone(null)
+ setZonesLoaded(true)
+ if (!data.pull_zones?.length) {
+ toast.error('No pull zones found for this API key')
+ }
+ } catch (err) {
+ toast.error(getAuthErrorMessage(err as Error) || 'Failed to load pull zones')
+ } finally {
+ setLoadingZones(false)
+ }
+ }
+
+ const handleConnect = async () => {
+ if (!selectedZone) {
+ toast.error('Please select a pull zone')
+ return
+ }
+ setConnecting(true)
+ try {
+ await connectBunny(siteId, apiKey.trim(), selectedZone.id, selectedZone.name)
+ toast.success('BunnyCDN connected successfully')
+ onConnected()
+ } catch (err) {
+ toast.error(getAuthErrorMessage(err as Error) || 'Failed to connect BunnyCDN')
+ } finally {
+ setConnecting(false)
+ }
+ }
+
+ return (
+
+
+
+
+
+ setApiKey(e.target.value)}
+ placeholder="Enter your BunnyCDN API key"
+ className="flex-1 px-3 py-2 text-sm bg-neutral-900 border border-neutral-700 rounded-lg text-white placeholder:text-neutral-500 focus:outline-none focus:border-neutral-500"
+ />
+
+
+
+
+ {zonesLoaded && pullZones.length > 0 && (
+
+
+
+
+
+
+
+ )}
+
+ {zonesLoaded && pullZones.length > 0 && (
+
+ )}
+
+
+ )
+}
+
export default function SiteIntegrationsTab({ siteId }: { siteId: string }) {
const { data: gscStatus, mutate: mutateGSC } = useGSCStatus(siteId)
const { data: bunnyStatus, mutate: mutateBunny } = useBunnyStatus(siteId)
+ const [showBunnySetup, setShowBunnySetup] = useState(false)
const handleConnectGSC = async () => {
try {
@@ -81,8 +255,7 @@ export default function SiteIntegrationsTab({ siteId }: { siteId: string }) {
}
const handleConnectBunny = () => {
- // Redirect to full settings page for BunnyCDN setup (requires API key input)
- window.location.href = `/sites/${siteId}/settings?tab=integrations`
+ setShowBunnySetup(true)
}
const handleDisconnectBunny = async () => {
@@ -90,12 +263,15 @@ export default function SiteIntegrationsTab({ siteId }: { siteId: string }) {
try {
await disconnectBunny(siteId)
await mutateBunny()
+ setShowBunnySetup(false)
toast.success('BunnyCDN disconnected')
} catch (err) {
toast.error(getAuthErrorMessage(err as Error) || 'Failed to disconnect')
}
}
+ const bunnyConnected = bunnyStatus?.connected ?? false
+
return (
@@ -113,17 +289,31 @@ export default function SiteIntegrationsTab({ siteId }: { siteId: string }) {
onConnect={handleConnectGSC}
onDisconnect={handleDisconnectGSC}
connectLabel="Connect with Google"
- />
+ >
+ {gscStatus?.connected && }
+
+
{ (e.target as HTMLImageElement).style.display = 'none' }} />}
name="BunnyCDN"
description="Monitor bandwidth, cache hit rates, and CDN performance."
- connected={bunnyStatus?.connected ?? false}
- detail={bunnyStatus?.connected ? `Pull zone: ${bunnyStatus.pull_zone_name || 'connected'}` : undefined}
+ connected={bunnyConnected}
+ detail={bunnyConnected ? `Pull zone: ${bunnyStatus?.pull_zone_name || 'connected'}` : undefined}
onConnect={handleConnectBunny}
onDisconnect={handleDisconnectBunny}
- />
+ >
+ {!bunnyConnected && showBunnySetup && (
+ {
+ mutateBunny()
+ setShowBunnySetup(false)
+ }}
+ />
+ )}
+
+
)