Enhance onboarding flow to support Google Calendar integration with Composio. Add state management for Google Calendar connection status, loading states, and connection handling. Update UI components to reflect Google Calendar connectivity in onboarding steps.

This commit is contained in:
tusharmagar 2026-03-16 20:24:01 +05:30
parent c81a04b497
commit 71e2e43ed1
3 changed files with 118 additions and 11 deletions

View file

@ -8,8 +8,8 @@ interface CompletionStepProps {
} }
export function CompletionStep({ state }: CompletionStepProps) { export function CompletionStep({ state }: CompletionStepProps) {
const { connectedProviders, granolaEnabled, slackEnabled, gmailConnected, handleComplete } = state const { connectedProviders, granolaEnabled, slackEnabled, gmailConnected, googleCalendarConnected, handleComplete } = state
const hasConnections = connectedProviders.length > 0 || granolaEnabled || slackEnabled || gmailConnected const hasConnections = connectedProviders.length > 0 || granolaEnabled || slackEnabled || gmailConnected || googleCalendarConnected
return ( return (
<div className="flex flex-col items-center justify-center text-center flex-1"> <div className="flex flex-col items-center justify-center text-center flex-1">
@ -76,6 +76,17 @@ export function CompletionStep({ state }: CompletionStepProps) {
<span>Gmail (Email)</span> <span>Gmail (Email)</span>
</motion.div> </motion.div>
)} )}
{googleCalendarConnected && (
<motion.div
initial={{ opacity: 0, x: -8 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.52 }}
className="flex items-center gap-2 text-sm text-muted-foreground"
>
<CheckCircle2 className="size-4 text-green-600 dark:text-green-400" />
<span>Google Calendar</span>
</motion.div>
)}
{connectedProviders.includes('google') && ( {connectedProviders.includes('google') && (
<motion.div <motion.div
initial={{ opacity: 0, x: -8 }} initial={{ opacity: 0, x: -8 }}

View file

@ -1,4 +1,4 @@
import { Loader2, CheckCircle2, ArrowLeft } from "lucide-react" import { Loader2, CheckCircle2, ArrowLeft, Calendar } from "lucide-react"
import { motion } from "motion/react" import { motion } from "motion/react"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { Switch } from "@/components/ui/switch" import { Switch } from "@/components/ui/switch"
@ -91,6 +91,7 @@ export function ConnectAccountsStep({ state }: ConnectAccountsStepProps) {
slackDiscovering, slackDiscoverError, slackDiscovering, slackDiscoverError,
handleSlackEnable, handleSlackSaveWorkspaces, handleSlackDisable, handleSlackEnable, handleSlackSaveWorkspaces, handleSlackDisable,
useComposioForGoogle, gmailConnected, gmailLoading, gmailConnecting, handleConnectGmail, useComposioForGoogle, gmailConnected, gmailLoading, gmailConnecting, handleConnectGmail,
useComposioForGoogleCalendar, googleCalendarConnected, googleCalendarLoading, googleCalendarConnecting, handleConnectGoogleCalendar,
handleNext, handleBack, handleNext, handleBack,
} = state } = state
@ -112,11 +113,11 @@ export function ConnectAccountsStep({ state }: ConnectAccountsStepProps) {
</div> </div>
) : ( ) : (
<div className="space-y-6"> <div className="space-y-6">
{/* Email / Email & Calendar */} {/* Email & Calendar */}
{(useComposioForGoogle || providers.includes('google')) && ( {(useComposioForGoogle || useComposioForGoogleCalendar || providers.includes('google')) && (
<div className="space-y-3"> <div className="space-y-3">
<span className="text-xs font-semibold text-muted-foreground uppercase tracking-wider"> <span className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">
{useComposioForGoogle ? 'Email' : 'Email & Calendar'} Email & Calendar
</span> </span>
{useComposioForGoogle ? ( {useComposioForGoogle ? (
<ProviderCard <ProviderCard
@ -141,6 +142,18 @@ export function ConnectAccountsStep({ state }: ConnectAccountsStepProps) {
index={cardIndex++} index={cardIndex++}
/> />
)} )}
{useComposioForGoogleCalendar && (
<ProviderCard
name="Google Calendar"
description="Sync calendar events for scheduling awareness"
icon={<Calendar className="size-5" />}
iconBg="bg-blue-500/10"
iconColor="text-blue-500"
providerState={{ isConnected: googleCalendarConnected, isLoading: googleCalendarLoading, isConnecting: googleCalendarConnecting }}
onConnect={handleConnectGoogleCalendar}
index={cardIndex++}
/>
)}
</div> </div>
)} )}

View file

@ -74,6 +74,12 @@ export function useOnboardingState(open: boolean, onComplete: () => void) {
const [composioApiKeyOpen, setComposioApiKeyOpen] = useState(false) const [composioApiKeyOpen, setComposioApiKeyOpen] = useState(false)
const [composioApiKeyTarget, setComposioApiKeyTarget] = useState<'slack' | 'gmail'>('gmail') const [composioApiKeyTarget, setComposioApiKeyTarget] = useState<'slack' | 'gmail'>('gmail')
// Composio/Google Calendar state
const [useComposioForGoogleCalendar, setUseComposioForGoogleCalendar] = useState(false)
const [googleCalendarConnected, setGoogleCalendarConnected] = useState(false)
const [googleCalendarLoading, setGoogleCalendarLoading] = useState(true)
const [googleCalendarConnecting, setGoogleCalendarConnecting] = useState(false)
const updateProviderConfig = useCallback( const updateProviderConfig = useCallback(
(provider: LlmProviderFlavor, updates: Partial<{ apiKey: string; baseURL: string; model: string; knowledgeGraphModel: string }>) => { (provider: LlmProviderFlavor, updates: Partial<{ apiKey: string; baseURL: string; model: string; knowledgeGraphModel: string }>) => {
setProviderConfigs(prev => ({ setProviderConfigs(prev => ({
@ -125,8 +131,17 @@ export function useOnboardingState(open: boolean, onComplete: () => void) {
console.error('Failed to check composio-for-google flag:', error) console.error('Failed to check composio-for-google flag:', error)
} }
} }
async function loadComposioForGoogleCalendarFlag() {
try {
const result = await window.ipc.invoke('composio:use-composio-for-google-calendar', null)
setUseComposioForGoogleCalendar(result.enabled)
} catch (error) {
console.error('Failed to check composio-for-google-calendar flag:', error)
}
}
loadProviders() loadProviders()
loadComposioForGoogleFlag() loadComposioForGoogleFlag()
loadComposioForGoogleCalendarFlag()
}, [open]) }, [open])
// Load LLM models catalog on open // Load LLM models catalog on open
@ -337,6 +352,47 @@ export function useOnboardingState(open: boolean, onComplete: () => void) {
} }
}, [startGmailConnect]) }, [startGmailConnect])
// Load Google Calendar connection status (Composio)
const refreshGoogleCalendarStatus = useCallback(async () => {
try {
setGoogleCalendarLoading(true)
const result = await window.ipc.invoke('composio:get-connection-status', { toolkitSlug: 'googlecalendar' })
setGoogleCalendarConnected(result.isConnected)
} catch (error) {
console.error('Failed to load Google Calendar status:', error)
setGoogleCalendarConnected(false)
} finally {
setGoogleCalendarLoading(false)
}
}, [])
// Connect to Google Calendar via Composio
const startGoogleCalendarConnect = useCallback(async () => {
try {
setGoogleCalendarConnecting(true)
const result = await window.ipc.invoke('composio:initiate-connection', { toolkitSlug: 'googlecalendar' })
if (!result.success) {
toast.error(result.error || 'Failed to connect to Google Calendar')
setGoogleCalendarConnecting(false)
}
} catch (error) {
console.error('Failed to connect to Google Calendar:', error)
toast.error('Failed to connect to Google Calendar')
setGoogleCalendarConnecting(false)
}
}, [])
// Handle Google Calendar connect button click
const handleConnectGoogleCalendar = useCallback(async () => {
const configResult = await window.ipc.invoke('composio:is-configured', null)
if (!configResult.configured) {
setComposioApiKeyTarget('gmail')
setComposioApiKeyOpen(true)
return
}
await startGoogleCalendarConnect()
}, [startGoogleCalendarConnect])
// New step flow: // New step flow:
// Rowboat path: 0 (welcome) → 2 (connect) → 3 (done) // Rowboat path: 0 (welcome) → 2 (connect) → 3 (done)
// BYOK path: 0 (welcome) → 1 (llm setup) → 2 (connect) → 3 (done) // BYOK path: 0 (welcome) → 1 (llm setup) → 2 (connect) → 3 (done)
@ -414,6 +470,11 @@ export function useOnboardingState(open: boolean, onComplete: () => void) {
refreshGmailStatus() refreshGmailStatus()
} }
// Refresh Google Calendar Composio status if enabled
if (useComposioForGoogleCalendar) {
refreshGoogleCalendarStatus()
}
if (providers.length === 0) return if (providers.length === 0) return
const newStates: Record<string, ProviderState> = {} const newStates: Record<string, ProviderState> = {}
@ -440,7 +501,7 @@ export function useOnboardingState(open: boolean, onComplete: () => void) {
} }
setProviderStates(newStates) setProviderStates(newStates)
}, [providers, refreshGranolaConfig, refreshSlackConfig, refreshGmailStatus, useComposioForGoogle]) }, [providers, refreshGranolaConfig, refreshSlackConfig, refreshGmailStatus, useComposioForGoogle, refreshGoogleCalendarStatus, useComposioForGoogleCalendar])
// Refresh statuses when modal opens or providers list changes // Refresh statuses when modal opens or providers list changes
useEffect(() => { useEffect(() => {
@ -480,12 +541,16 @@ export function useOnboardingState(open: boolean, onComplete: () => void) {
const cleanup = window.ipc.on('oauth:didConnect', async (event) => { const cleanup = window.ipc.on('oauth:didConnect', async (event) => {
if (event.provider === 'rowboat' && event.success) { if (event.provider === 'rowboat' && event.success) {
// Re-check the composio-for-google flag now that the account is connected // Re-check composio flags now that the account is connected
try { try {
const result = await window.ipc.invoke('composio:use-composio-for-google', null) const [googleResult, calendarResult] = await Promise.all([
setUseComposioForGoogle(result.enabled) window.ipc.invoke('composio:use-composio-for-google', null),
window.ipc.invoke('composio:use-composio-for-google-calendar', null),
])
setUseComposioForGoogle(googleResult.enabled)
setUseComposioForGoogleCalendar(calendarResult.enabled)
} catch (error) { } catch (error) {
console.error('Failed to re-check composio-for-google flag:', error) console.error('Failed to re-check composio flags:', error)
} }
setCurrentStep(2) // Go to Connect Accounts setCurrentStep(2) // Go to Connect Accounts
} }
@ -519,6 +584,17 @@ export function useOnboardingState(open: boolean, onComplete: () => void) {
toast.error(error || 'Failed to connect to Gmail') toast.error(error || 'Failed to connect to Gmail')
} }
} }
if (toolkitSlug === 'googlecalendar') {
setGoogleCalendarConnected(success)
setGoogleCalendarConnecting(false)
if (success) {
toast.success('Connected to Google Calendar')
} else {
toast.error(error || 'Failed to connect to Google Calendar')
}
}
}) })
return cleanup return cleanup
@ -650,6 +726,13 @@ export function useOnboardingState(open: boolean, onComplete: () => void) {
handleConnectGmail, handleConnectGmail,
handleComposioApiKeySubmit, handleComposioApiKeySubmit,
// Composio/Google Calendar state
useComposioForGoogleCalendar,
googleCalendarConnected,
googleCalendarLoading,
googleCalendarConnecting,
handleConnectGoogleCalendar,
// Navigation // Navigation
handleNext, handleNext,
handleBack, handleBack,