diff --git a/src/components/mika/integrations/DisconnectMCPDialog.tsx b/src/components/mika/integrations/DisconnectMCPDialog.tsx
index 7551e16..3d923a4 100644
--- a/src/components/mika/integrations/DisconnectMCPDialog.tsx
+++ b/src/components/mika/integrations/DisconnectMCPDialog.tsx
@@ -20,6 +20,7 @@ import { invokeFunction } from "@/lib/invoke-function";
interface Props {
open: boolean;
onOpenChange: (open: boolean) => void;
+ onDisconnected?: () => void;
integrationId: string;
mcpSlug: string;
mcpName: string;
@@ -28,6 +29,7 @@ interface Props {
export function DisconnectMCPDialog({
open,
onOpenChange,
+ onDisconnected,
integrationId,
mcpSlug,
mcpName,
@@ -49,6 +51,7 @@ export function DisconnectMCPDialog({
runtime_sync_warning?: string | null;
}>("disconnect-integration", {
integration_id: integrationId,
+ force_pause_jobs: dependentJobs.length > 0,
});
setSubmitting(false);
if (error) {
@@ -56,12 +59,16 @@ export function DisconnectMCPDialog({
return;
}
toast.success(`${mcpName} desconectado.`);
+ if ((data?.paused_jobs_count ?? 0) > 0) {
+ toast.info(`${data!.paused_jobs_count} automação(ões) pausada(s).`);
+ }
if (data?.runtime_sync_warning) {
toast.warning("Integração removida, mas o runtime do agente não sincronizou.");
}
queryClient.invalidateQueries({ queryKey: ["user-integrations"] });
queryClient.invalidateQueries({ queryKey: ["user-integration-limits"] });
onOpenChange(false);
+ onDisconnected?.();
}
return (
@@ -101,7 +108,7 @@ export function DisconnectMCPDialog({
)}
- Pause ou exclua essas automações antes de desconectar.
+ Elas serão pausadas automaticamente ao desconectar.
@@ -117,7 +124,7 @@ export function DisconnectMCPDialog({
e.preventDefault();
handleDisconnect();
}}
- disabled={submitting || isLoading || hasActiveJobs}
+ disabled={submitting || isLoading}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
{submitting ? "Desconectando..." : "Desconectar"}
diff --git a/src/routes/painel.integracoes.$slug.tsx b/src/routes/painel.integracoes.$slug.tsx
index cee8c57..3df275f 100644
--- a/src/routes/painel.integracoes.$slug.tsx
+++ b/src/routes/painel.integracoes.$slug.tsx
@@ -102,14 +102,19 @@ function IntegrationDetailPage() {
async function handleTest() {
setTesting(true);
- const { data, error } = await invokeFunction<{ ok: boolean; account?: string }>(
+ const { data, error } = await invokeFunction<{
+ ok?: boolean;
+ success?: boolean;
+ account?: string;
+ account_info?: unknown;
+ }>(
"test-integration",
{ integration_id: integration!.id },
);
setTesting(false);
if (error) {
toast.error(error.message);
- } else if (data?.ok) {
+ } else if (data?.ok || data?.success) {
toast.success("Conexão funcionando perfeitamente.");
} else {
toast.warning("Conexão respondeu, mas com aviso. Verifique status.");
@@ -289,18 +294,8 @@ function IntegrationDetailPage() {
{
- setDisconnectOpen(o);
- if (!o) {
- // se desconectou, volta para a lista
- queryClient.invalidateQueries({ queryKey: ["user-integrations"] }).then(() => {
- const stillConnected = integs.some((i) => i.id === integration.id);
- if (!stillConnected) {
- navigate({ to: "/painel/integracoes", search: {} });
- }
- });
- }
- }}
+ onOpenChange={setDisconnectOpen}
+ onDisconnected={() => navigate({ to: "/painel/integracoes", search: {} })}
integrationId={integration.id}
mcpSlug={mcp.slug}
mcpName={mcp.name}
diff --git a/supabase/functions/_shared/oauth-providers.ts b/supabase/functions/_shared/oauth-providers.ts
index 677adc9..8170424 100644
--- a/supabase/functions/_shared/oauth-providers.ts
+++ b/supabase/functions/_shared/oauth-providers.ts
@@ -23,19 +23,44 @@ export interface ProviderEnv {
redirectUri: string;
}
+function readFirstEnv(keys: string[]): string {
+ for (const key of keys) {
+ const value = Deno.env.get(key);
+ if (value) return value;
+ }
+ return "";
+}
+
export function getProviderEnv(slug: ProviderSlug, redirectUri: string): ProviderEnv {
- const map: Record = {
- google_workspace: ["GOOGLE_CLIENT_ID", "GOOGLE_CLIENT_SECRET"],
- notion: ["NOTION_CLIENT_ID", "NOTION_CLIENT_SECRET"],
- todoist: ["TODOIST_CLIENT_ID", "TODOIST_CLIENT_SECRET"],
- calcom: ["CALCOM_CLIENT_ID", "CALCOM_CLIENT_SECRET"],
- microsoft_365: ["MICROSOFT_CLIENT_ID", "MICROSOFT_CLIENT_SECRET"],
+ const map: Record = {
+ google_workspace: {
+ id: ["GOOGLE_CLIENT_ID", "GOOGLE_OAUTH_CLIENT_ID"],
+ secret: ["GOOGLE_CLIENT_SECRET", "GOOGLE_OAUTH_CLIENT_SECRET"],
+ },
+ notion: {
+ id: ["NOTION_CLIENT_ID", "NOTION_OAUTH_CLIENT_ID"],
+ secret: ["NOTION_CLIENT_SECRET", "NOTION_OAUTH_CLIENT_SECRET"],
+ },
+ todoist: {
+ id: ["TODOIST_CLIENT_ID", "TODOIST_OAUTH_CLIENT_ID"],
+ secret: ["TODOIST_CLIENT_SECRET", "TODOIST_OAUTH_CLIENT_SECRET"],
+ },
+ calcom: {
+ id: ["CALCOM_CLIENT_ID", "CALCOM_OAUTH_CLIENT_ID"],
+ secret: ["CALCOM_CLIENT_SECRET", "CALCOM_OAUTH_CLIENT_SECRET"],
+ },
+ microsoft_365: {
+ id: ["MICROSOFT_CLIENT_ID", "MICROSOFT_OAUTH_CLIENT_ID"],
+ secret: ["MICROSOFT_CLIENT_SECRET", "MICROSOFT_OAUTH_CLIENT_SECRET"],
+ },
};
- const [idKey, secretKey] = map[slug];
- const clientId = Deno.env.get(idKey) ?? "";
- const clientSecret = Deno.env.get(secretKey) ?? "";
+ const keys = map[slug];
+ const clientId = readFirstEnv(keys.id);
+ const clientSecret = readFirstEnv(keys.secret);
if (!clientId || !clientSecret) {
- throw new Error(`Credenciais OAuth ausentes para ${slug} (${idKey}/${secretKey})`);
+ throw new Error(
+ `Credenciais OAuth ausentes para ${slug} (${keys.id.join(" ou ")}/${keys.secret.join(" ou ")})`,
+ );
}
return { clientId, clientSecret, redirectUri };
}
@@ -415,8 +440,8 @@ export async function revokeToken(
}
case "todoist": {
// Todoist precisa client_id/secret + access_token no body
- const clientId = Deno.env.get("TODOIST_CLIENT_ID") ?? "";
- const clientSecret = Deno.env.get("TODOIST_CLIENT_SECRET") ?? "";
+ const clientId = readFirstEnv(["TODOIST_CLIENT_ID", "TODOIST_OAUTH_CLIENT_ID"]);
+ const clientSecret = readFirstEnv(["TODOIST_CLIENT_SECRET", "TODOIST_OAUTH_CLIENT_SECRET"]);
const body = new URLSearchParams({
client_id: clientId,
client_secret: clientSecret,
diff --git a/supabase/functions/disconnect-integration/index.ts b/supabase/functions/disconnect-integration/index.ts
index 7080753..3aedb51 100644
--- a/supabase/functions/disconnect-integration/index.ts
+++ b/supabase/functions/disconnect-integration/index.ts
@@ -88,7 +88,7 @@ Deno.serve(async (req) => {
// 1. Checa dependências
const { data: dependentJobs } = await admin
.from("scheduled_jobs")
- .select("id, name")
+ .select("id, name, status")
.eq("user_id", userId)
.neq("status", "archived")
.contains("required_mcp_slugs", [slug]);
@@ -104,7 +104,8 @@ Deno.serve(async (req) => {
}
let pausedCount = 0;
- if (dependentJobs && dependentJobs.length > 0 && force_pause_jobs) {
+ const jobsToPause = (dependentJobs ?? []).filter((job) => job.status === "active");
+ if (jobsToPause.length > 0 && force_pause_jobs) {
const { error: pErr } = await admin
.from("scheduled_jobs")
.update({
@@ -112,9 +113,10 @@ Deno.serve(async (req) => {
auto_paused_reason: `Integração ${slug} foi desconectada`,
})
.eq("user_id", userId)
+ .eq("status", "active")
.neq("status", "archived")
.contains("required_mcp_slugs", [slug]);
- if (!pErr) pausedCount = dependentJobs.length;
+ if (!pErr) pausedCount = jobsToPause.length;
}
// 2. Busca tokens ANTES de qualquer delete