@@ -281,7 +322,7 @@ function Inspector({ connection }: InspectorProps) {
/* Main content with search and tool call */
@@ -404,6 +445,4 @@ function Inspector({ connection }: InspectorProps) {
)}
);
-}
-
-export default Inspector;
+};
diff --git a/apps/web/src/components/integrations/detail/new.tsx b/apps/web/src/components/integrations/detail/new.tsx
deleted file mode 100644
index 4d4a70840c..0000000000
--- a/apps/web/src/components/integrations/detail/new.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import { DetailForm } from "./form.tsx";
-
-export default function NewIntegration() {
- return
;
-}
diff --git a/apps/web/src/components/integrations/list/breadcrumb.tsx b/apps/web/src/components/integrations/list/breadcrumb.tsx
index 7fe75bdce7..8f33d2ea63 100644
--- a/apps/web/src/components/integrations/list/breadcrumb.tsx
+++ b/apps/web/src/components/integrations/list/breadcrumb.tsx
@@ -1,8 +1,22 @@
-import { useIntegrations, useMarketplaceIntegrations } from "@deco/sdk";
+import {
+ useCreateIntegration,
+ useIntegrations,
+ useMarketplaceIntegrations,
+} from "@deco/sdk";
import { Button } from "@deco/ui/components/button.tsx";
import { Icon } from "@deco/ui/components/icon.tsx";
-import { ReactNode } from "react";
-import { Link, useMatch } from "react-router";
+import { Spinner } from "@deco/ui/components/spinner.tsx";
+import {
+ AlertDialog,
+ AlertDialogAction,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle,
+} from "@deco/ui/components/alert-dialog.tsx";
+import { ReactNode, useState } from "react";
+import { Link, useMatch, useNavigate } from "react-router";
import { useBasePath } from "../../../hooks/useBasePath.ts";
import { PageLayout } from "../../pageLayout.tsx";
@@ -30,45 +44,88 @@ function BreadcrumbItem({
}
export function IntegrationPage({ children }: { children: ReactNode }) {
+ const navigate = useNavigate();
const withBasePath = useBasePath();
const connected = useMatch({ path: "/integrations" });
+ const [error, setError] = useState
(null);
+ const create = useCreateIntegration();
const { data: installedIntegrations } = useIntegrations();
const { data: marketplaceIntegrations } = useMarketplaceIntegrations();
+ const handleCreate = async () => {
+ try {
+ const result = await create.mutateAsync({});
+ navigate(withBasePath(`/integration/${result.id}`));
+ } catch (err) {
+ setError(
+ err instanceof Error ? err.message : "Failed to create integration",
+ );
+ }
+ };
+
return (
-
-
-
-
+ <>
+
+
+
+
-
-
-
-
- >
- }
- >
- {children}
-
+ >
+ }
+ />
+
setError(null)}>
+
+
+ Error
+
+ {error}
+
+
+
+ setError(null)}>
+ OK
+
+
+
+
+ >
);
}
diff --git a/apps/web/src/components/pageLayout.tsx b/apps/web/src/components/pageLayout.tsx
index 4ec7ad0fbf..b6ec949f56 100644
--- a/apps/web/src/components/pageLayout.tsx
+++ b/apps/web/src/components/pageLayout.tsx
@@ -2,12 +2,20 @@ import { Button } from "@deco/ui/components/button.tsx";
import { Icon } from "@deco/ui/components/icon.tsx";
import { ScrollArea } from "@deco/ui/components/scroll-area.tsx";
import { useSidebar } from "@deco/ui/components/sidebar.tsx";
-import { ReactNode } from "react";
+import { cn } from "@deco/ui/lib/utils.ts";
+import {
+ ComponentProps,
+ ComponentType,
+ createContext,
+ ReactNode,
+ use,
+} from "react";
+import Docked, { togglePanel, useDock } from "./dock/index.tsx";
import { TeamSelector } from "./sidebar/TeamSelector.tsx";
export interface PageProps {
- header: ReactNode;
- children: ReactNode;
+ header?: ReactNode;
+ main: ReactNode;
footer?: ReactNode;
}
@@ -37,20 +45,22 @@ export function MobileTopbar() {
* and an optional footer. The header and footer take only the space they need, while the content
* area takes up the remaining space and becomes scrollable when content overflows.
*/
-export function PageLayout({ header, children, footer }: PageProps) {
+export function PageLayout({ header, main, footer }: PageProps) {
return (
-
+ {header && (
+
+ )}
@@ -63,3 +73,66 @@ export function PageLayout({ header, children, footer }: PageProps) {
);
}
+
+interface MainViewProps {
+ header?: ComponentType
;
+ main: ComponentType;
+ footer?: ComponentType;
+}
+
+export interface DockedPageProps {
+ main: MainViewProps;
+ tabs: Record;
+ initialOpen?: boolean;
+ }>;
+}
+
+const Context = createContext(null);
+
+function MainView() {
+ const mainView = use(Context);
+
+ if (!mainView) {
+ return null;
+ }
+
+ const { header: Header, main: Main, footer: Footer } = mainView;
+
+ return (
+ }
+ main={}
+ footer={Footer && }
+ />
+ );
+}
+
+export function DockedPageLayout({ main, tabs }: DockedPageProps) {
+ return (
+
+
+
+ );
+}
+
+export function DockedToggleButton(
+ { id, title, children, className, ...btnProps }: {
+ id: string;
+ title: string;
+ children: ReactNode;
+ } & ComponentProps,
+) {
+ const { openPanels, availablePanels } = useDock();
+
+ return (
+
+ );
+}
diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx
index 4f3cdbe617..595c56066e 100644
--- a/apps/web/src/main.tsx
+++ b/apps/web/src/main.tsx
@@ -20,10 +20,6 @@ import { ErrorBoundary, useError } from "./ErrorBoundary.tsx";
import { trackException } from "./hooks/analytics.ts";
import { About } from "./components/about/index.tsx";
-const IntegrationNew = lazy(() =>
- import("./components/integrations/detail/new.tsx")
-);
-
const IntegrationEdit = lazy(() =>
import("./components/integrations/detail/edit.tsx")
);
@@ -161,10 +157,6 @@ function Router() {
path="integrations"
element={} />}
/>
- } />}
- />
} />}
diff --git a/packages/sdk/src/hooks/mcp.ts b/packages/sdk/src/hooks/mcp.ts
index 54378c4cbb..fe71fbb825 100644
--- a/packages/sdk/src/hooks/mcp.ts
+++ b/packages/sdk/src/hooks/mcp.ts
@@ -25,7 +25,7 @@ export const useCreateIntegration = () => {
const { context: root } = useSDK();
const create = useMutation({
- mutationFn: (mcp: Integration) => createIntegration(root, mcp),
+ mutationFn: (mcp: Partial) => createIntegration(root, mcp),
onSuccess: (result) => {
const key = getKeyFor(root, result.id);