import type { IntegrationSetupState } from "@rakazo/contracts"; import { useRouter } from "expo-router"; import { useEffect, useState } from "react"; import { ActivityIndicator, Linking, Pressable, ScrollView, StyleSheet, Text, TextInput, View, } from "react-native"; import { rpc } from "../lib/api"; import { mobileTokens } from "../lib/appearance"; import { useI18n } from "../lib/i18n"; import { useThemedStyles } from "../lib/native"; export default function IntegrationSetup() { const router = useRouter(); const { t } = useI18n(); const styles = useThemedStyles(createStyles); const [state, setState] = useState(null); const [choice, setChoice] = useState("direct"); const [key, setKey] = useState(""); const [clientId, setClientId] = useState(""); const [projectId, setProjectId] = useState(""); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); useEffect(() => { void rpc("integrationSetup/get") .then((setup) => { if (!setup.canConfigure) { router.replace("/integrations"); return; } setState(setup); }) .catch(() => setError(t("Could not load integrations"))); }, []); const choices = [ { id: "direct", label: t("Direct MCP") }, { id: "composio", label: "Composio" }, { id: "pipedream", label: "Pipedream" }, { id: "executor", label: "Executor" }, ]; const managed = choice === "composio" || choice === "pipedream"; const configured = state?.providers.find((provider) => provider.id === choice)?.configured; async function save() { setBusy(true); setError(null); try { await rpc( "integrationSetup/save", choice === "composio" ? { provider: "composio", apiKey: key } : { provider: "pipedream", clientId, clientSecret: key, projectId, environment: "production", }, ); setKey(""); router.replace("/integrations"); } catch { setError(t("Could not verify or save these credentials")); } finally { setBusy(false); } } function button(label: string, onPress: () => void, disabled = false) { return ( {label} ); } if (!state) return error ? {error} : ; return ( {choices.map(({ id, label }) => ( { setChoice(id); setKey(""); setError(null); }} style={[styles.choice, choice === id && styles.selected]} > {label} ))} {choice === "composio" || choice === "pipedream" ? ( <> {configured ? {t("Connected")} : null} {state?.canConfigure ? ( <> {choice === "pipedream" ? ( <> {t("Client ID")} {t("Project ID")} ) : null} {choice === "composio" ? t("API key") : t("Client secret")} {button(t("Get credentials"), () => { void Linking.openURL( choice === "composio" ? "https://dashboard.composio.dev" : "https://pipedream.com/docs/connect/mcp/developers", ); })} ) : state && !configured ? ( {t("Ask the server owner to configure this provider.")} ) : null} ) : ( <> {choice === "executor" ? t("Set up Executor on your server in the web app.") : t("Finish MCP authorization in the web app.")} {button( t("Open web app"), () => { const url = new URL(state.webUrl); if (choice === "direct") url.searchParams.set("mode", "mcp"); void Linking.openURL(url.toString()); }, !state, )} )} {busy ? : null} {error ? ( {error} ) : null} {button( t("Continue"), () => { if (managed && key.trim()) void save(); else router.replace("/"); }, busy || (managed && state?.canConfigure && !configured && (!key.trim() || (choice === "pipedream" && (!clientId.trim() || !projectId.trim())))), )} {button(t("Skip"), () => router.replace("/"), busy)} ); } function createStyles() { const tokens = mobileTokens(); return StyleSheet.create({ content: { padding: 20, gap: 16 }, choices: { borderWidth: 1, borderColor: tokens.border, borderRadius: 12, overflow: "hidden" }, choice: { padding: 16 }, selected: { backgroundColor: tokens.muted }, text: { color: tokens.foreground, fontSize: 16 }, input: { borderWidth: 1, borderColor: tokens.border, borderRadius: 10, padding: 12, color: tokens.foreground, }, button: { padding: 14, borderRadius: 10, backgroundColor: tokens.muted }, buttonText: { color: tokens.foreground, textAlign: "center", fontSize: 16 }, error: { color: tokens.destructive }, }); }